No toolchains found in the NDK toolchains folder for ABI with prefix: mips64el-linux-android


원인 : 오래된 flutter 플젝을 열때 나오는 버그이다. 

Open android/gradle/gradle-wrapper.properties and change this line:

distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip

to this line:

distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip

Open android/build.gradle and change this line:

classpath 'com.android.tools.build:gradle:3.0.1'

to this:

classpath 'com.android.tools.build:gradle:3.1.2'


NodeJS Advance 2주차 스터디

부산에서 매주 진행되는 NodeJS Advance 스터디입니다.

더 많은 스터디는 네이버 카페에서 확인 부탁드립니다.

스터디 내용은 Udemy 강좌를 같이 보고 정리를 한 글입니다.

1주차 스터디 내용

이번 스터디에서는 노드의 성능향상에 대해서 공부하도록 한다.

  • Cluster 모드를 사용
  • Worker Threads를 사용

우선 테스트를 위해 express 를 사용해보자.

const express = require('express');
const app = express();

app.get('/', (req, res) => {
    res.send('Hi there');
});

app.listen(3000);

위와 같이 구성될 경우 일반적이라고 볼수 있는데 이건 그림으로 표현하자면

1543129280118

의도적 딜레이

만약 요청을 5초정도로 하는 경우 동시에 실행시 어떻게 되는지 체크 해보자.

const express = require('express');
const app = express();

function doWork(duration) {
    const start = Date.now();
    while(Date.now() - start < duration) {

    }
}

app.get('/', (req, res) => {
    doWork(5000);
    res.send('Hi there');
});

app.listen(3000);

하나만 실행했을 경우 5초가 걸릴것이다.

만약 2개를 동시에 실행시 처음 5초가 끝나고 두번째가 실행된다.

  • 2번째를 동시 실행시 8.57초가 걸리는 걸 볼수 있다.

1543129512873

이를 해결하기 위해 Cluster 라는 방법을 사용할 수 있다.

1543129640802

Cluster 동작원리

  • 처음 index.js 를 호출시 cluster manager를 통해서 cluster.fork() 를 실행
  • 그 후 다시 index.js 로 가서 worker instance를 실행한다.

1543129735477

소스로 구현해보자.

  • cluster.fork()를 통해서 다시 index.js 를 실행해서 프로세스를 새로 만든다.
const cluster = require('cluster');

if(cluster.isMaster) {
    cluster.fork();
} else {
    const express = require('express');
    const app = express();

    function doWork(duration) {
        const start = Date.now();
        while(Date.now() - start < duration) {

        }
    }

    app.get('/', (req, res) => {
        doWork(5000);
        res.send('Hi there');
    });

    app.listen(3000);

}

다중 클러스터

만약 Cluster 를 여러개 설정시 어떻게 될까?

cluster.fork();
cluster.fork();
cluster.fork();

테스트시 다른 탭에 있는 2개의 쓰레드가 빨리 도는 걸 확인할수 있다는데...잘 안된다...;;

AB 테스트

맥이나 리눅스에서 AB 테스팅할 수 있는 방법이 있다고 한다.

50번의 요청을 동시에 50번을 날리도록 실행

ab -c 50 -n 50 localhost:3000/fast

1543131006116

문제점

클러스터를 실행하더라도 한정된 자원이 있기 때문에 점점 가성비가 떨어진다고 한다.

이번 시간에는 클러스트 개념과 기본 사용법에 대해서 살펴보았다.

참석해주셔서 감사합니다.

도커&쿠버네티스 7주차 스터디 정리내용

부산에서 매주 진행되는 스터디입니다.

부산에서 다른 스터디 내용을 보실려면 카페 에서 보실수 있습니다.

https://www.udemy.com/docker-and-kubernetes-the-complete-guide 을 공부하고 있습니다

1주차 스터디

2주차 스터디

3주차 스터디

4주차 스터디

5주차 스터디

6주차 스터디

이번주 스터디 내용

이번주는 저번 시간에 이어서 Dockerhub로 올린 이미지를 aws에 beanstalk로 올리는 걸 진행하도록 하겠다.

4주차에는 바로 도커이미지를 aws beanstalk 로 올리는 걸 진행했었다.

이제는 실제 Production에 사용되는 형태로 진행하는 걸 배워볼 것이다.

여기서 진행된 소스는 여기 Github 에서 받을수 있다.

배우게 되는 내용

  • AWS EC2 Task Definition
  • AWS VPS 등 기본 설정

1542771228782

AWS EB 설정

aws eb에 여러개의 도커 이미지(client, serverworker)등을 올리기 위해서는 설정 파일이 필요하다.

  • Dockerrun.aws.json

1542764486465

1542764529514

AWS ECS Task Definition

Beanstalk 에는 어떻게 컨테이너들이 동작될지를 알수가 없다. 그래서 aws ec2에서는 task definition 기능을 통해서 이미지를 어떻게 동작될지를 정의를 할 수 있다.

1542764875545

실습

/Dockerrun.aws.json

Dockerhub 에 올라간 tag/name 을 확인하자.

  • name : 이미지 이름

  • image : 도커 허브에 올라간 이미지 이름

  • hostname : docker-compose.yml 에 있는 이름

  • essential : 만약 콘테이너가 어떤 이유로 문제가 생겼을 경우 연관되어 있는 다른 컨테이너들을 같이 내릴건지 여부 ( true 이면 다같이 내린다. )

  • memory : 각 컨터이너마다 얼마나 메모리를 할당할건지 지정

  • portMapping : 외부 포트와 내부 콘테이너 포트를 바인딩

  • links : 콘테이너가 연관되어 있는 걸 연결한다.

    1542765885341

{
    "AWSEBDockerrunVersion" : 2,
    "containerDefinitions" : [
        {
            "name": "client",
            "image": "bear2u/multi-client",
            "hostname": "client",
            "essential": false,
            "memory": 128
        },
        {
            "name": "server",
            "image": "bear2u/multi-server",
            "hostname": "api",
            "essential": false,
            "memory": 128
        },
        {
            "name": "worker",
            "image": "bear2u/multi-worker",
            "hostname": "worker",
            "essential": false,
            "memory": 128
        },
        {
            "name": "nginx",
            "image": "bear2u/multi-nginx",
            "hostname": "nginx",
            "essential": true,
            "portMappings": [
                {
                    "hostPort": 80,
                    "containerPort": 80
                }
            ],
            "links": ["client", "server"],
            "memory": 128
        }
    ]
}

Aws Beanstalk

AWS 콘솔에 들어가서 새로운 어플리케이션을 만들어준다.

  • multi-docker

1542766325151

create one > Multi-container Docker 선택

1542766420194

1542766452949

1542766606837

Prod. 구조

Dockerrun.aws.json 내부에는 따로 postgres 와 redis 에 관한 DB 콘테이너가 없다. 그러면 어떻게 설정할까?

기존에 배웠던 개발 구조

1542766660010

배우게 될 배포(Production) 구조



위 그림에서 보다시피 Redis 는 AWS Elastic cache 로 빠져있고 postgres 는 aws rds 로 빼서 사용하고 있는 걸 볼수 있다. 가능하다면 이런 구조를 만드는게 더 이점이 많다고 한다.

이렇게 빼는 이유에 대해서 알아보자.

AWS Elastic Cache (Redis)

  • 자동으로 redis 인스턴스를 만들어주고 유지보수 해준다.
  • 스케일링하기 쉽다.
  • 로깅 및 유지관리
  • 보안성이 우리가 하는것보다 전문가들이 하기 때문에 더 안전하다.

AWS RDS (Postgres)

  • 자동으로 postgres 인스턴스를 만들수 있고 유지보수 하기도 쉽다.

  • 스케일링 하기 쉽다.

  • 로깅 및 유지보수하기 쉽다.

  • 우리가 하는것보다 안전하다.

  • 자동으로 백업 및 롤백 지원

다음에는 내부 콘테이너에 넣어서 하는 방법에 대해서도 공부할 예정이다.

EB와 DB 연결

1542767378510

현재로써는 EB에서는 RDS와 EC 연결을 서로 알수 없는 구조이다. 이걸 연결하기 위해서는 aws 콘솔에서 서로 묶어주는 게 필요하다.

1542767495818

1542767712822

VPC

VPC 에 가보면 MultiDocker-env 라는 security group 이 만들어진 걸 볼 수 있다.

1542767849145

EB 와 DB 간의 연결

1542767924722

Postgres 생성

  1. RDS (postgres`) 를 만든다.

하단에 프리티어 체크 박스를 꼭 활성화를 해주자.

1542768159192

  1. Postgress 기본 입력
  • identifier: multi-docker-postgress
  • username : postgres
  • password : postgresspassword

1542768322944

1542768448718

1542768508822

Elastic chache (Redis) 생성

1542768613773

Node type 값을 t2로 선택을 하는 걸 꼭 유의하자.

1542768704289

1542769330034

1542769364030

Create Security Group

1542769789607

1542769829241

inbound 설정

1542769905067

각 서비스 Security Group 지정

  • 방금 만든 security 설정을 elasticache와 rds 에 지정해준다.

REDIS VPC 지정

1542770208533

RDS 수정

1542770335728

1542770365368

1542770404982

Elastic Beanstalk 수정

1542770512070

1542770547234

EB 환경설정

1542770672978

1542770953026

Redis Host

1542770773324

RDS Host

1542770894210

AWS IAM 키 설정

Service 에서 IAM 검색해서 들어가자

1542771348365

1542771412892

1542771460621

1542771499669

Travis Setting

Travis -> github -> id 설정

1542771598675

Travis.yml deploy 설정

sudo: required
services:
  - docker

before_install:
  - docker build -t bear2u/react-test -f ./client/Dockerfile.dev ./client

script:
  - docker run bear2u/react-test npm test -- --coverage

after_success:
  - docker build -t bear2u/multi-client ./client
  - docker build -t bear2u/multi-nginx ./nginx
  - docker build -t bear2u/multi-server ./server
  - docker build -t bear2u/multi-worker ./worker
  # Log in to the docker CLI
  - echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_ID" --password-stdin
  # Take those images and push them to docker hub
  - docker push bear2u/multi-client
  - docker push bear2u/multi-nginx
  - docker push bear2u/multi-server
  - docker push bear2u/multi-worker

# add this
deploy:
  provider: elasticbeanstalk
  region: ap-northeast-2
  app: multi-docker
  env: MultiDocker-env
  bucket_name : elasticbeanstalk-ap-northeast-2-234249885524
  bucket_path : docker-multi
  on:
    branch: master
  access_key_id: $AWS_ACCESS_KEY
  secret_access_key:
    secure: $AWS_SECRET_KEY

배포

이제 github master 브랜치에 수정된 내용을 push를 해보고 travis와 beanstalk 에서 결과를 확인하자.

1542772294954

만약에 beanstalk 에 문제가 발생한 경우 Logs 를 통해서 확인 가능하다.

1542772467846

1542772646987

Cleaning Service

테스팅을 완료했으면 aws 리소스들을 삭제하자.

  • beanstalk
  • rds
  • redis
  • iam
  • security group
  • vpn

여기까지 7주차 스터디 내용이다. 다음주부터 본격적으로 쿠버네티스를 통해 관리하는 걸 진행하도록 한다.

참석해주셔서 감사합니다.

NodeJS Advance 1주차 스터디 정리 #2

부산에서 매주 진행되는 NodeJS Advance 스터디입니다.

더 많은 스터디는 네이버 카페에서 확인 부탁드립니다.

스터디 내용은 Udemy 강좌를 같이 보고 정리를 한 글입니다.

이벤트 루프 내부 구조

이전 글에도 있지만 다시 내용을 살펴보자.

  • pendingTimers
  • pendingOSTasks
  • pendingOperations
// node myFile.js

const pendingTimers = [];
const pendingOSTasks = [];
const pendingOperations = [];

// New timbers, tasks, operations are recorded from myFile running
myFile.runContents();

function shouldContinue() {
    // Check one : Any pending setTimeout, setInterval, setImmediate?
    // Check two: Any pending OS tasks? (Like server listening to port)
    // Check three: Any pending long running operations? (Like fs module)
    return pendingTimers.length || pendingOSTasks.length || pendingOperations.length;
}
// Entire body executes in one 'tick'
while(shouldContinue()) {
    // 1) Node looks at pendintTimers and sees if any functions
    // are ready to be called. setTimeout, setInterval

    // 2) Node looks at pendingOSTasks and pendingOperations
    // and calls relevant callbacks

    // 3) Pause execution. Continue when ....
    // - a new pendingOSTask is done
    // - a new pendingOperation is done
    // - a timer is about to complete

    // 4) Look at pendingTimers. Call any setImmediate

    // 5) Handle any 'close' events
}

// exit back to terminal

이번글에서는 이러한 3가지 내용에 대해서 자세히 공부해보도록 한다.

노드는 싱글(단일) 쓰레드일까?

  • 이벤트 루프는 기본적으로 싱글쓰레드가 맞다.
  • 어떤 라이버러리들은 싱글쓰레드가 아니다.



1. Libuv Threads - pendingOperation

예를 들어 pbkdf2 함수를 예를 들어보자.

const crypto = require('crypto');

const start = Date.now();
crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
    console.log('1:', Date.now() - start);
});

crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
    console.log('2:', Date.now() - start);
});

crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
    console.log('3:', Date.now() - start);
});

crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
    console.log('4:', Date.now() - start);
});


.....................
4: 1050
1: 1072
3: 1089
2: 1112

보시다 시피 동시에 4개의 쓰레드가 동작이 된다. 만약 싱글쓰레드라고 가정하면 각각 1초씩 진행이 되어야 하고 총 4초가 걸려야 한다.

1542683601432

이유는 Libuv 내에서는 자체 Thread Pool 을 가지고 있다.

1542683727534

기본적으로 한개의 쓰레드풀에 4개의 쓰레드가 있으며 이건 Size를 늘리거나 줄일 수 있다.

process.env.UV_THREADPOOL_SIZE = 5

기본이 4개인걸 테스팅 해보자.

총 6번을 실행시 1~4번째까지는 1초가 걸리고 5번째 부터는 첫번째 내용이 끝나면 빈자리로 들어가는 걸 볼 수 있다.

const crypto = require('crypto');

const start = Date.now();
crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
    console.log('1:', Date.now() - start);
});

crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
    console.log('2:', Date.now() - start);
});

crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
    console.log('3:', Date.now() - start);
});

crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
    console.log('4:', Date.now() - start);
});

crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
    console.log('5:', Date.now() - start);
});

crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
    console.log('6:', Date.now() - start);
});

.......

4: 1050
1: 1072
3: 1089
2: 1112 
6: 1946 <-- 새로운 1초가 걸리는 걸 확인 할수 있다.
5: 1956

1542684077427

그러면 Thread Pool 에 대해 궁금한 점이 있을수 있다.

  • 자바스크립트를 위해 쓰레드 풀을 사용하거나 오직 nodejs 함수를 통해서 사용가능한가?

Custom JS 를 통해서 쓰레드 풀를 사용을 할수 있다.

  • 어떤 std 라이버러리 함수를 통해서 쓰레드 풀을 사용할 수 있는가?

모든 fs 모듈 함수를 통해서 사용 가능하다. 그리고 몇개의 crypto 함수들은 OS에 종속되어 있다.

  • 그럼 이벤트 루프에서 이러한 쓰레드풀이 어떻게 사용되어 지는가?

처음 글에서 봤던 이벤트 루프 함수 내 pendingOperations 으로 사용되어 진다.

2. OS's async - pendingOSTasks

1542685060681

Operation Thread 와는 다르게 운영체제에서 직접 실행하는 내용이다.

즉 쓰레드를 이용하지 않고 다른 영역에서 호출된다고 보면 된다.

여러개를 호출해도 한번에 실행되는 걸 볼수 있다.

const https = require('https');

const start = Date.now();

function doRequest() {
    https.request('https://www.google.com', res => {
        res.on('data', () => {});
        res.on('end', () => {
            console.log(Date.now() - start);
        });
    })
    .end();
}

doRequest();
doRequest();
doRequest();
doRequest();
doRequest();
doRequest();
doRequest();
doRequest();

...............
582
591
601
612
630
637
639
667

이에 대해 FAQ를 정리해보자.

  • node에서 std 라이버러리에서 OS's async 기능을 사용할 수 있나?

  • 거의 모든 네트워킹 관련된건 OS's async 기능을 이용한다.

  • 이벤트 루프내에서 이 기능이 사용되어 지는가?

  • pendingOSTasks로 사용되어 진다.

노드JS 흐름도

1542685666194

NodeJS 쓰레드 우선 순위

만약에 thread pool 과 os's async이 만날 경우 어떤 순서로 실행될까?

const https = require('https');
const crypto = require('crypto');
const fs = require('fs');

const start = Date.now();

function doRequest() {
    https.request('https://www.google.com', res => {
        res.on('data', () => {});
        res.on('end', () => {
            console.log(Date.now() - start);
        });
    })
    .end();
}

function doHash(no) {
    crypto.pbkdf2('a', 'b', 100000, 512, 'sha512', () => {
        console.log(`Hash #${no}:`, Date.now() - start);
    });
}

#1
doRequest();

#3
fs.readFile('multitask.js', 'utf8', () => {
    console.log('FS:', Date.now() - start);
});

#2
doHash(1);
#4
doHash(2);
#5
doHash(3);
#6
doHash(4);

............

633
Hash #2: 965
FS: 966
Hash #1: 971
Hash #3: 990
Hash #4: 1010

우선 OS 가 먼저 실행되고 파일읽는게 먼저 실행되었는데 HASH 함수가 먼저 실행된 걸 볼수 있다.

  1. OS 실행 - doRequest()

  2. fs.readFile 실행

  3. ThreadPool 에 redFile 함수 등록

    1542686207545

  4. readFile 함수가 OS 단으로 빠짐 - 파일 읽어야 해서

  5. 그 쓰레드 번호에 다른 남아있는 HASH 함수가 등록됨

    1542686285333

  6. 그리고 readFile 함수가 끝나면 처리가 완료된다.

  7. 마지막으로 나머지 HASH 값이 처리가 된다.

여기까지 이번 주차에서 공부한 내용이다.

중요한 점은 다음과 같다.

  1. OS's async
  2. Thread Pool
  3. Thread Pool size 조절 가능함
  4. fs 사용시 순서 고려해야함

이상으로 1주차 노드 중급 스터디 정리 내용입니다.

참석해주셔서 감사합니다.

NodeJS Advance 1주차 스터디 정리

부산에서 매주 진행되는 NodeJS Advance 스터디입니다.

더 많은 스터디는 네이버 카페에서 확인 부탁드립니다.

스터디 내용은 Udemy 강좌를 같이 보고 정리를 한 글입니다.들어가며

Node를 안지 벌써 2년이 넘은것 같다. 하지만 늘 express 로 rest api 서버로만 개발만 하다 좀 더 알고 싶은 마음이 항상 있었다. 그래서 스터디 주제도 중급 노드JS로 정해서 진행하기로 했다.

NodeJS 기본 구조

  • Javascript code 작성
  • NodeJS
  • V8 또는 libuv 에서 처리

1542544892846

1542544957069

Crypto 안에 있는 pbkdf2 함수를 통해서 동작원리를 파악할 예정이다.

1542545130428

NodeJS 소스내에 pbkdf2.js 파일을 살펴보자.

소스위치

1542545471505

crypto 라는 걸 통해서 process.binding 을 해주는 걸 볼 수 있다.

c++ 로 구현된 걸 가져온 걸로 보인다. 그럼 c++ 구현 내부를 살펴보면...

node /src / node_crypto.cc

1542546067536

1542546170025

좀 더 내부 과정을 살펴보면 아래 그림과 같다고 한다.


추가적으로 c++ 와 자바스크립트 사이에 V8 를 통해서 중개해주고 있는 걸 볼 수 있다.

node /src / node_crypto.cc

1542546297557

libuv

이건 좀 더 운영체제와 관련된 내용인데 나중에 공부하도록 한다.

Thread

이벤트 루프로 들어가기전 사전지식인 Thread 에 대해서 공부하고 넘어가도록 한다.

일반적인 싱글 프로세스 형태

1542546531155

멀티 프로세스 형태

1542546560867

윈도우의 경우 작업관리자를 열고 살펴보면 Thread 갯수를 확인할수 있다.

1542546634555

스케쥴링

운영체제는 특점 시점에서 처리할 쓰레드를 결정할 수 있다.

1542546732269

하나이상의 코어가 있는 경우 여러 쓰레드를 쉽게 빠르게 처리를 할 수 있다.

1542546854687

만약에 2개의 쓰레드가 있다고 가정하고 한개의 쓰레드가 I/O 로 시간이 걸린다고 했을 경우 스케쥴러에서 따로 뺀 공간에서 처리를 하고 2번이 끝나고 1을 다시 넣어서 마무리를 할 수 있다.

1542547055938

추후 다시 한번 소스를 보면서 쓰레드 관련된 내용을 살펴볼 예정이다.

Event loop

이벤트 루프는 한 쓰레드가 무엇을 해야하는지 결정하는 제어 구조와 같다고 생각하면 된다.

1542547167583

전체적인 이벤트 루프 구조를 Pseudo code 로 살펴보자.

크게 3가지 제어 구문이 끝나야 이벤트 루프가 끝난다.

  • pendingTimers
    • Check Any pending setTimeout, setInterval, setImmediate?
  • pendingOSTasks
    • Check Any pending OS tasks? (Like server listening to port)
  • pendingOperations
    • Check Any pending long running operations? (Like fs module)
// node myFile.js

const pendingTimers = [];
const pendingOSTasks = [];
const pendingOperations = [];

// New timbers, tasks, operations are recorded from myFile running
myFile.runContents();

function shouldContinue() {
    // Check one : Any pending setTimeout, setInterval, setImmediate?
    // Check two: Any pending OS tasks? (Like server listening to port)
    // Check three: Any pending long running operations? (Like fs module)
    return pendingTimers.length || pendingOSTasks.length || pendingOperations.length;
}
// Entire body executes in one 'tick'
while(shouldContinue()) {
    // 1) Node looks at pendintTimers and sees if any functions
    // are ready to be called. setTimeout, setInterval

    // 2) Node looks at pendingOSTasks and pendingOperations
    // and calls relevant callbacks

    // 3) Pause execution. Continue when ....
    // - a new pendingOSTask is done
    // - a new pendingOperation is done
    // - a timer is about to complete

    // 4) Look at pendingTimers. Call any setImmediate

    // 5) Handle any 'close' events
}

// exit back to terminal

다음 글을 통해서 각각의 내용에 대해서 자세히 살펴볼 예정이다.

참석해주셔서 감사합니다.

CSS Next grid 클론 ( 1 ~ 5 )

부산에서 매주 진행되는 CSS 초중급 스터디입니다.

더 많은 스터디는 네이버 카페에서 확인 부탁드립니다.

이 내용은 니콜라스님의 CSS 마스터 클래스를 공부하고 정리한 글입니다.

이번주는 1 ~ 5번 페이지까지 공부했습니다. 

Github 소스

1

img

2

1542443044415

3

1542443152744

4

1542443329569

5

1542443350752

'스터디 > WEB' 카테고리의 다른 글

Clipping CSS - Day3  (0) 2019.05.22
Sexy Typograping - Day2  (0) 2019.05.19
PostCSS 스터디 정리 ( 2주차 )  (0) 2018.11.12
Web 디자인시 유의 할 점 정리  (0) 2018.11.09
CSS Next 정리 ( 부산 1주차 HTML + CSS 스터디 )  (0) 2018.11.07

Flutter WhatsApp 클론 (부산 4주차 스터디)

부산에서 매주 진행되는 Flutter 스터디 4주차 내용입니다.

더 많은 부산에서 스터디 정보는 네이버 카페 에서 확인 가능합니다.

소스는 Github 에서 확인 가능합니다.



1주차

2주차

3주차

스터디 내용

이번주차는 저번 스터디에 이어서 로직부분을 진행하도록 하겠다.

  • Firestore
  • FireBase Auth
    • Google Sigin
  • FireBase Storage - 이미지 업로드

FireStore

Firebase 에서 제공하는 Collection - Document 형태의 nosql realdb 이다.

자세한 설치 및 내용은 공식문서를 참조하자.

시나리오 #1

채팅방에서 텍스트박스에 내용을 입력하면 Firestore에 내용이 입력되고 그 입력된 내용을 StreamBuilder 로 통해서 구독을 해서 리스트뷰에 자동 업데이트를 하는 게 목표

Firestore 저장

  /// firestore연결해서 저장
  saveChat(ChatData chatData) {
    Firestore.instance.collection(fireStoreCollectionName)
        .document()
        .setData(chatData.toMap());
  }

Firestore 구독 및 리스트 조회

  • Firestore snapshot 스트림이용해서 구독처리
  Widget _buildListItems() {
    return StreamBuilder(
      stream: Firestore.instance.collection("room").snapshots(),
        builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
          if(!snapshot.hasData) return Text("Loading...");
          List<ChatData> list = snapshot.data.documents.map((DocumentSnapshot document) {
            return ChatData.fromMap(document);
          }).toList();
          print(list);
          return ListView.builder(
              controller: _scrollController,
              itemCount: list.length,
              itemBuilder: (BuildContext context, int index) => _generateItems(list[index])
          );
        }
    );
  }

시나리오 #2

구글 로그인을 하는데 오른쪽 프로필 이미지를 로그인 후 변하도록 하고 다시 클릭시 로그아웃 다이얼로그 창이 나오도록 하자.

image-20181114124552756

  • Firebase Auth 라이버러리 추가

    • firebase_auth: "0.6.2+1"

화면 구성

  • 상단 바 구성
    • title, profile image 등으로 구성되어 있다.
Widget _buildMatchAppbar(context) {
  return AppBar(
    title: Row(
      mainAxisAlignment: MainAxisAlignment.start,
      children: <Widget>[
        Flexible(
          child: Container(
            width: double.maxFinite,
            child: Text(
              "Flutter Study App",
              style: TextStyle(color: AppBarColor),
            ),
          ),
        ),
        _getProfileCircleImage()
      ],
    ),
    backgroundColor: AppBackgroundColor,
  );
}
  • Profile Image
    • 로그인 상태에는 로그아웃 다이얼로그가 나오게 하고
    • 로그아웃 상태에서는 로그인 다이얼로그가 나오게 한다.
_getProfileCircleImage() {
  return InkWell(
    onTap: () {
      userData == null
      ? _handleSignIn()
      : _showSignOutDialog();
    },
    child: new Container(
      width: 30.0,
      height: 30.0,
      decoration: new BoxDecoration(
        color: const Color(0xff7c94b6),
        image: new DecorationImage(
          image: userData == null
            ? new AssetImage("assets/images/profile.png")
            : new NetworkImage(userData.photoUrl),
          fit: BoxFit.cover,
        ),
        borderRadius: new BorderRadius.all(new Radius.circular(30.0)),
      ),
    ),
  );
}

유의할 점

  • Firebase Console 설정 페이지에서 Sha 값을 등록해줘야 한다.
  • image-20181114125547305

시나리오 #3

Image Picker (카메라 또는 갤러리) 를 통해서 가져온 이미지를 Firestorage로 업로드 한다.

  • Firebase Stroage
  • Image Picker

image-20181114154240806

포토 아이콘을 클릭시 Image Picker 가 실행된다.

Source를 어떤걸 주냐에 따라 차이가 날 수 있다. (GalleryCamera)

void _uploadImage() async {
    File imageFile = await ImagePicker.pickImage(source: ImageSource.camera);
    int timeStamp = DateTime.now().millisecondsSinceEpoch;
    StorageReference storageReference = FirebaseStorage
        .instance
        .ref()
        .child("img_" + timeStamp.toString() + ".jpg");
    StorageUploadTask uploadTask = storageReference.putFile(imageFile);
    uploadTask.onComplete
        .then((StorageTaskSnapshot snapShot) async {
          String imgUrl = await snapShot.ref.getDownloadURL();

          final chatData = ChatData(
              message: null,
              time: _getCurrentTime(),
              delivered: true,
              sender: userData.displayName,
              senderEmail: userData.email,
              senderPhotoUrl: userData.photoUrl,
              imgUrl: imgUrl
          );

          fbApiProvider.saveChat(chatData);

        });
  }

마지막으로 Bubble (풍선) 쪽을 살펴보도록 하자.

import 'package:flutter/material.dart';

class Bubble extends StatelessWidget {
  Bubble({this.message, this.time, this.delivered, this.isOthers, this.profilePhotoUrl, this.imageUrl});

  final String message, time, profilePhotoUrl, imageUrl;
  final delivered, isOthers;

  @override
  Widget build(BuildContext context) {
    final bg = isOthers ? Colors.white : Colors.greenAccent.shade100;
    final align = isOthers ? MainAxisAlignment.start : MainAxisAlignment.end;
    final icon = delivered ? Icons.done_all : Icons.done;
    final radius = isOthers
        ? BorderRadius.only(
      topRight: Radius.circular(5.0),
      bottomLeft: Radius.circular(10.0),
      bottomRight: Radius.circular(5.0),
    )
        : BorderRadius.only(
      topLeft: Radius.circular(5.0),
      bottomLeft: Radius.circular(5.0),
      bottomRight: Radius.circular(10.0),
    );
    return Row(
      mainAxisAlignment: align,
      children: <Widget>[
        _getCircleProfileIcon(),
        Container(
          //margin: const EdgeInsets.all(3.0),
          margin: isOthers
            ? EdgeInsets.only(top: 50.0, left: 3.0, bottom: 3.0, right: 3.0)
            : EdgeInsets.all(3.0),
          padding: const EdgeInsets.all(8.0),
          decoration: BoxDecoration(
            boxShadow: [
              BoxShadow(
                  blurRadius: .5,
                  spreadRadius: 1.0,
                  color: Colors.black.withOpacity(.12))
            ],
            color: bg,
            borderRadius: radius,
          ),
          child: Stack(
            children: <Widget>[
              Padding(
                padding: EdgeInsets.only(right: 48.0),
                child: message != null
                  ? Text(message)
                  : Image.network(imageUrl, width: 100.0,)
              ),
              Positioned(
                bottom: 0.0,
                right: 0.0,
                child: Row(
                  children: <Widget>[
                    Text(time,
                        style: TextStyle(
                          color: Colors.black38,
                          fontSize: 10.0,
                        )),
                    SizedBox(width: 3.0),
                    Icon(
                      icon,
                      size: 12.0,
                      color: Colors.black38,
                    )
                  ],
                ),
              )
            ],
          ),
        )
      ],
    );
  }

  _getCircleProfileIcon() {
    if(!isOthers) {
      return Container();
    }
    return Container(
      width: 30.0,
      height: 30.0,
      decoration: new BoxDecoration(
        color: const Color(0xff7c94b6),
        image: new DecorationImage(
          image: new NetworkImage(profilePhotoUrl),
          fit: BoxFit.cover,
        ),
        borderRadius: new BorderRadius.all(new Radius.circular(30.0)),
      ),
    );
  }
}

이상으로 부산에서 진행된 Flutter 입문 스터디 내용이었습니다.

다음 주는 Flutter 프로젝트 스터디를 진행할 예정입니다. 관심있으신분은 신청 부탁드립니다.


PostCSS 공부 ( 부산 스터디 2주차 ) 정리

부산에서 매주 진행되는 CSS 초중급 스터디입니다.

더 많은 스터디는 네이버 카페에서 확인 부탁드립니다.

이 내용은 니콜라스님의 CSS 마스터 클래스를 공부하고 정리한 글입니다.

Github 소스링크

저번 시간에는 CSS Next 에 대해서 정리를 했었다.

이번 스터디는 PostCSS 에 대해서 공부 하도록 한다.

공부할 내용은 아래와 같다.

  • Parcel

  • PostCSS & Parcel

  • pseudo class

  • Css Variables

  • @custom media and media query ranges

  • color-mod, gray(), System-ui

  • Nesting Rules

  • 기타 추천 사이트들

Parcel

코드를 압축,변환등을 해주는 툴이다. CSS 를 컴파일해서 어디든 사용할 수 있는 CSS로 바꿀수 있다.

공식 홈페이지는 https://parceljs.org/ 이다.

우선 디펜시즈를 설치를 하자. 노드를 사용할 예정이다.

npm install -g parcel-bundler

그럼 index.html 을 컴파일해서 변경시

parcel index.html
body {
   background-color: blue;
}

package.json

{
 "name": "web_css",
 "version": "1.0.0",
 "description": "",
 "main": "index.js",
 "scripts": {
   "start": "parcel index.html"
},
 "author": "",
 "license": "ISC"
}
npm run start
$ npm run start

> web_css@1.0.0 start G:\workspace_study\web_css
> parcel index.html

Server running at http://localhost:1234
√ Built in 2.73s.

서버가 실행되는 걸 볼 수 있다.

그럼 이걸 이용해서 최신 CSS를 어떻게 사용할 수 있는지 살펴보도록 하자.

PostCSS & PostCSS Preset Env

PostCSS 는 플러그인으로 좀 더 편하게 CSS 개발을 할 수 있도록 도와주는 역할을 한다.

자바스크립트로 말하면 타입스크립트 같은 ...

자세한 설명은 링크를 참고하도록 하자.

플러그인으로 할수 있는게 상당히 다양하다고 한다.

우리가 이용할건 PostCSS preset env 를 이용할 것이다.

특이한 점은 stage 0~3 까지 나뉘어져 있으며 단계별로 사용할 수 있는 게 다르다.

https://preset-env.cssdb.org/

https://preset-env.cssdb.org/features#all-property

npm install postcss-preset-env

package.json

  "postcss": {
  "plugins":{
    "postcss-preset-env": {
      "stage":0
    }
  }
}

style.css

@custom-media --viewport-medium (width <= 50rem);
@custom-selector :--heading h1, h2, h3, h4, h5, h6;

:root {
 --fontSize: 1rem;
 --mainColor: #12345678;
 --secondaryColor: lab(32.5 38.5 -47.6 / 90%);
}

html {
 overflow: hidden auto;
}

@media (--viewport-medium) {
 body {
   color: var(--mainColor);
   font-family: system-ui;
   font-size: var(--fontSize);
   line-height: calc(var(--fontSize) * 1.5);
   overflow-wrap: break-word;
   padding-inline: calc(var(--fontSize) / 2 + 1px);
}
}

:--heading {
 margin-block: 0;
}

.hero:matches(main, .main) {
 background-image: image-set("img/background.jpg" 1x, "img/background-2x.jpg" 2x);
}

a {
 color: rgb(0 0 100% / 90%);

&:hover {
   color: rebeccapurple;
}
}

실행시 보기 편한 CSS로 컴파일 되서 나오는 걸 볼수 있다.

:root {
 --fontSize: 1rem;
 --mainColor: rgba(18,52,86,0.47059);
 --secondaryColor: rgba(102, 51, 153, 0.9);
}

html {
 overflow-x: hidden;
 overflow-y: auto;
 overflow: hidden auto;
}

@media (max-width: 50rem) {
 body {
   color: rgba(18,52,86,0.47059);
   color: var(--mainColor);
   font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, sans-serif;
   font-size: 1rem;
   font-size: var(--fontSize);
   line-height: calc(1rem * 1.5);
   line-height: calc(var(--fontSize) * 1.5);
   word-wrap: break-word;
   padding-left: calc(1rem / 2 + 1px);
   padding-right: calc(1rem / 2 + 1px);
   padding-left: calc(var(--fontSize) / 2 + 1px);
   padding-right: calc(var(--fontSize) / 2 + 1px);
}
}

h1,h2,h3,h4,h5,h6 {
 margin-top: 0;
 margin-bottom: 0;
}

main.hero, .hero.main {
 background-image: img/background.jpg;
}

@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {

main.hero, .hero.main {
 background-image: img/background-2x.jpg;
}
}

main.hero, .hero.main {
 background-image: -webkit-image-set(url("img/background.jpg") 1x, url("img/background-2x.jpg") 2x);
 background-image: image-set("img/background.jpg" 1x, "img/background-2x.jpg" 2x);
}

a {
 color: rgba(0, 0, 255, 0.9)
}

a:hover {
   color: #639;
}

Pseudo-classes

    <nav>
       <ul>
           <li><a href="">Link</a></li>
           <li><a href="">Link</a></li>
           <li class="target"><a href="">Link</a></li>
           <li><a href="">Link</a></li>
           <li><a href="">Link</a></li>
       </ul>
   </nav>
li:matches(:nth-child(even), .target) {
   background-color: blue;
}
  • li태그에 대해서 짝수번째만 선택 하고 , target 클래스가 있는걸 동시에 배경을 변경하도록 한다.

빨간 박스는 target 이어서 선택이 된것을 볼 수 있다.

not 태그

exclude 한 태그들을 선택가능하다.

li:not(.target) {
   background-color: blue;
}

CSS Variables

css에 변수를 지정해서 할당하는 것도 가능하다.

  • 여러번 반복되는 복사, 붙여넣기할때 유용하다.

:root {
   --awesomeColor:red;
}

li:first-child a{
   color:var(--awesomeColor);
}

Custom Selector

@custom-selector :--headers h1,h2,h3,h4,h5;

:--headers {
   color: blue;
}

Custom media & range

850px width 밑으로는 배경색을 red 로 지정

@custom-media --ipad-size (max-width: 850px);

@media(--ipad-size) {
   body {
       background-color: red;
  }
}

range

@custom-media --ipad-size (450px < width <= 850px);

@media(--ipad-size) {
   body {
       background-color: red;
  }
}

컴파일 되서 나오는 결과는 아래와 같이 나온다.

@media(min-width: 451px) and (max-width: 850px) {
   body {
       background-color: red;
  }
}

color mod

h1 {
   color: color-mod(#8e44ad, alpha(50%))
   color: color-mod(#8e44ad, lightness(50%))
}

https://www.w3.org/TR/css-color-4/#modifying-colors

Grey

h1 {
   color: grey(90);
}

System-ui

h1 {
   font-family: system-ui;
}

Nesting Rules

내부적으로 this 개념을 & 을 통해 넣을 수 있다.

index.html

    <main>
      <section>
          <li><a href="#">Hello!</a></li>
      </section>
  </main>

style.css

main {
   background-color: blue;
  & section {
     background-color: red;
     width: 500px;
    & li {
       background-color: yellow;
       width: 400px;
      & a {
         color: black;
        &:hover {
           font-size: 30px;
        }
      }
    }
  }
}

a {
   color: red;
}

기타

여기까지 이론 수업이고 다음에는 실전으로 넘어가서 15개의 사이트를 클론하도록 한다.

이상으로 2주차 스터디 내용을 정리하였다.

참석해주셔서 감사합니다.

'스터디 > WEB' 카테고리의 다른 글

Sexy Typograping - Day2  (0) 2019.05.19
Css next grid 클론 ( 1 ~ 5 )  (0) 2018.11.17
Web 디자인시 유의 할 점 정리  (0) 2018.11.09
CSS Next 정리 ( 부산 1주차 HTML + CSS 스터디 )  (0) 2018.11.07
Flex Css 스터디 정리  (0) 2018.11.06


도커&쿠버네티스 6주차 스터디 정리내용

부산에서 매주 진행되는 스터디입니다.

부산에서 다른 스터디 내용을 보실려면 카페 에서 보실수 있습니다.

https://www.udemy.com/docker-and-kubernetes-the-complete-guide 을 공부하고 있습니다

1주차 스터디

2주차 스터디

3주차 스터디

4주차 스터디

5주차 스터디

스터디 내용

이번 6주차에는 이전 주차에서 공부한 내용을 기반으로 Client Production 을 Docker Hub 로 Travis를 통해서 자동 배포하는 걸 배울 예정이다.

그리고 Production 을 하는 방법에 대해서도 공부하도록 한다.

이전에 배운 내용(Single) 과 이번 주차 부터 배울 내용에 대한 Flow 를 알아보도록 하자.

Single Container Flow

  • Github 에 푸시
  • Travis 에 자동으로 레포를 웹훅으로 가져온다.
  • Travis에서 이미지를 빌드하고 코드들을 테스팅 한다.
  • AWE EB(ElasticBean) 에 Travis는 코드를 푸시한다.
  • EB는 이미지를 빌드하고 그걸 배포한다.

1541856986612

Multi Container Flow

  • Github 에 푸시
  • Travis에 자동으로 레포를 웹훅으로 가져온다.
  • Travis 는 이미지를 테스트하고 코드를 테스트한다.
  • **Travis 는 Production 이미지를 빌드한다. **
  • Travis 는 도커허브에 Prod. 이미지들을 푸시한다.
    • Docker Hub ID,PWD 는 따로 셋팅에서 저장한다.
  • Travis 는 AWS EB 에 프로젝트를 푸시한다.
  • EB는 도커허브로 부터 이미지들을 가져와서 배포한다.

1541857220104

사전 준비

자 그럼 시작을 해보자.

우선 소스를 이전꺼 다음으로 진행할텐데 Github checkout 으로 같이 시작을 할 수 있다.

> git clone https://github.com/bear2u/docker-study2.git
...
> git checkout 64b470215f24a1450cd7d70d72f79700f0781542
....

Dockerfile

worker / Dockerfile

Dockerfile.dev 파일 내용과 같으며 마지막에 커맨드 라인만 dev -> start 로 변경해준다.

FROM node:alpine
WORKDIR "/app"
COPY ./package.json ./
RUN npm install
COPY . .
CMD ["npm", "run", "start"]

server / Dockerfile

FROM node:alpine
WORKDIR "/app"
COPY ./package.json ./
RUN npm install
COPY . .
CMD ["npm", "run", "start"]

nginx / Dockerfile

FROM nginx
COPY ./default.conf /etc/ngnix/conf.d/default.conf

Client with another nginx

client 은 좀 복잡하다.

싱글 콘테이너를 만들때 nginx 와 react build를 통해서 서버를 구성했었다.

1541858471574

하지만 멀티 콘테이너를 구성시 외부에도 nginx 를 두고 내부에도 nginx 를 세팅해야 하는 걸 명심하자.

1541858595682

그럼 멀티 콘테이너 구성시 어떻게 하는지 알아보자.

Client 내부 ngnix 구성

client / nginxdefault.conf

3000 포트로 들어오도록 허용하며 index 파일을 메인으로 한다.

server {
    listen 3000;

    location / {
        root /usr/share/nginx/html;
        index index.html index.htm;
        try_files $uri $uri/ /index.html;
    }
}

client/Dockerfile

  • builder 이미지에 build 된 Prod 파일들을 넣고
  • nginx 설정을 복사를 하고
  • nginx 에 이전 builder 내 /app/build 폴더에 있는 Prod 파일들을 html 로 복사를 해준다.
FROM node:alpine as builder
WORKDIR '/app'
COPY ./package.json ./
RUN npm install
COPY . .
RUN npm run build

FROM nginx
EXPOSE 3000
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /app/build /usr/share/nginx/html

Client test 수정

현재로썬 Client Test 진행시 충돌이 발생될 수 있다고 한다. 당장은 비활성화를 하고 진행하도록 하자.

client / src/ App.test.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

it('renders without crashing', () => {});

이제 준비가 다 되었다. Travis 를 연결해서 배포를 해보자.

Travis + Github 연동

만약 새로운 프로젝트이면 Github에 저장소를 만들어서 푸시하고Travis 에서 활성화를 해주도록 하자.

  • Github 에 푸시

  • Travis 에서 연동

    • 만약 목록에 안나온다면 왼쪽 상단에 Sync Account를 클릭해서 동기화를 하자.

      1541860215515

1541860438426

Travis 설정

1541860575095

.travis.yml

주의점은 travis 설정시 태그를 꼭 도커 허브 아이디를 태그명으로 지정해줘야 한다.

sudo: required
services:
  - docker

before_install:
  - docker build -t bear2u/react-test -f ./client/Dockerfile.dev ./client

script:
  - docker run bear2u/react-test npm test -- --coverage

after_success:
  - docker build -t bear2u/multi-client ./client
  - docker build -t bear2u/multi-nginx ./nginx
  - docker build -t bear2u/multi-server ./server
  - docker build -t bear2u/multi-worker ./worker
  # Log in to the docker CLI
  - echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_ID" --password-stdin
  # Take those images and push them to docker hub
  - docker push bear2u/multi-client
  - docker push bear2u/multi-nginx
  - docker push bear2u/multi-server
  - docker push bear2u/multi-worker

Beanstalk 에 올리는 과정을 제외한 docker hub에 올리는 것까지 진행되었다.

도커 허브 관련해서 id, password 는 셋팅을 통해서 해준다.

Settings

1541861411570

그럼 Github에 푸시를 해본다.

Travis 가 정상적으로 연동되어서 hub 에 푸시가 되는 걸 확인 할 수 있다.

.... 


Time:        1.19s
Ran all test suites.
--------------------------|----------|----------|----------|----------|-------------------|
File                      |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
--------------------------|----------|----------|----------|----------|-------------------|
All files                 |        0 |        0 |        0 |        0 |                   |
 App.js                   |        0 |      100 |        0 |        0 |                10 |
 Fib.js                   |        0 |      100 |        0 |        0 |... 44,45,52,56,62 |
 OtherPage.js             |        0 |      100 |        0 |        0 |                 5 |
 index.js                 |        0 |        0 |        0 |        0 |     1,2,3,4,5,7,8 |
 registerServiceWorker.js |        0 |        0 |        0 |        0 |... 36,137,138,139 |
--------------------------|----------|----------|----------|----------|-------------------|
travis_time:end:2e40ccb6:start=1541861556459182738,finish=1541861559398870991,duration=29396882

......
......


ac7ed8526610: Pushed
5761fba18cc8: Pushed
e9aaefdfed5d: Pushed
latest: digest: sha256:2eadc96e313836b05b807d46c2692c2818856c11aa1aa15dde69edd2103a5315 size: 1782
travis_time:end:0578baae:start=1541861604426303298,finish=1541861609935082744,duration=5508779446
�[0Ktravis_fold:end:after_success.9
�[0K
Done. Your build exited with 0.

1541861811733

이렇게 도커 허브에 정상적으로 올라간 걸 볼수 있다.

여기까지 소스는 Github 에서 확인이 가능하다.

다음 시간에는 aws 의 beanstalk 으로 올려서 docker hub를 통해서 이미지를 가져와서 서비스를 실행하는 방법에 대해서 공부할 예정이다.

이상으로 도커 & 쿠버네티스 6주차 스터디 정리 내용이다.

참석해주셔서 감사합니다.

Web 디자인시 유의 할 점 정리

이 내용은 유데미 강좌를 보고 정리한 글입니다.

요약

  • 대부분의 콘텐츠는 텍스트이므로 멋진 타이포그래피는 멋진 웹 사이트의 핵심 요소입니다.

  • 이미지는 웹 디자인에서 점점 더 중요 해지고 있습니다. 따라서 훌륭한 이미지를 선택하고 그 위에 텍스트를 넣는 것이 작업의 필수적인 부분입니다.

  • 아이콘은 웹 사이트의 친숙한 음색을 설정하는 좋은 방법이지만 신중하게 사용하십시오.

  • 공백을 적절하게 사용하면 전문적으로 디자인 된 웹 사이트가 만들어 지므로 올바른 방법으로 많이 사용하십시오.

  • 콘텐츠의 시각적 계층 구조를 정의하여 레이아웃을 작성하십시오. 공백은 또한 이것을 위해 중요합니다.

  • 웹 사이트는 사용자와 웹 사이트 소유자가 목표를 달성 할 수 있도록 설계해야합니다. 이것은 사용자 경험입니다.

  • 다른 디자이너의 잘 디자인 된 웹 사이트를 연구하여 영감을 얻는 것이 매우 중요합니다.

웹 디자인시 참고할만한 치트 시트

아름다운 타이포그래피

  1. 본문 텍스트에 15 ~ 25 픽셀 사이의 글꼴 크기 사용

  1. 헤드 라인에 (실제로) 큰 글꼴 크기를 사용하십시오.

  1. 글꼴 크기의 120 ~ 150 % 사이의 줄 간격을 사용하십시오

  1. 한 줄당 45-90 자

  1. 좋은 글꼴 사용

  1. 웹 사이트에 대해 원하는 모양과 느낌을 반영하는 글꼴을 선택하십시오.

  1. 하나의 글꼴 만 사용하십시오.

프로처럼 색상 사용하기

  1. 하나의 기본 색상 만 사용하십시오.

  1. 더 많은 색상을 사용하려면 도구를 사용하십시오.

  2. 3.주의를 끌기 위해 색을 사용하십시오.

  1. 디자인에 검정색을 사용하지 마십시오.

  1. 색상을 현명하게 선택하십시오.

이미지 작업

  1. 텍스트를 이미지 위에 직접 놓습니다.

  1. 이미지를 중첩합니다.

  1. 텍스트를 상자에 넣으십시오.

  1. 이미지를 흐리게 처리합니다.

  1. 바닥이 퇴색한다.

아이콘 작업

  1. 아이콘을 사용하여 기능 / 단계를 나열하십시오.

  1. 작업 및 링크에 아이콘 사용

  1. 아이콘은 알아볼 수 있어야합니다.

  1. 아이콘에 레이블을 지정하십시오.

  1. 아이콘이 중심 무대가되어서는 안됩니다.

  1. 가능할 때마다 아이콘 글꼴 사용

간격 및 레이아웃

  1. 요소 사이에 공백 넣기

  1. 요소 그룹 사이에 공백을 넣으십시오.

  1. 웹 사이트 섹션 사이에 공백을 넣으십시오.

  1. 잠재 고객이 가장 먼저 바라는 위치를 정의하십시오.

  1. 귀하의 콘텐츠 메시지에 해당하는 흐름을 수립하십시오

  1. 공백을 사용하여 그 흐름을 만듭니다.


'스터디 > WEB' 카테고리의 다른 글

Sexy Typograping - Day2  (0) 2019.05.19
Css next grid 클론 ( 1 ~ 5 )  (0) 2018.11.17
PostCSS 스터디 정리 ( 2주차 )  (0) 2018.11.12
CSS Next 정리 ( 부산 1주차 HTML + CSS 스터디 )  (0) 2018.11.07
Flex Css 스터디 정리  (0) 2018.11.06

HTML + CSS 스터디 1주차 스터디 정리내용 #2

부산에서 매주 진행되는 CSS 초중급 스터디입니다.

더 많은 스터디는 네이버 카페에서 확인 부탁드립니다.

저번 시간은 Flex 에 대해서 공부했다.

Flex로 화면 배치 하는데 불편함이 다소 있다. 그래서 Css Grid 라는 게 나왔다.

CSS Grid 에 대해서 하나씩 정리해보도록 하겠다.

이 내용은 니콜라스님의 CSS 마스터 클래스를 공부하고 정리한 글이다.


CSS Grid 가 필요한 이유?

우선 FlexBox 가 부족한 부분이 뭔지 살펴보자.

만약 여러개의 박스가 들어올 경우 overflow 되면서 배치가 되기 때문에 보기가 안좋을 수 있다.

CSS Grid System 이라고 하는 테이블같은 걸 쉽게 짤 수 있도록 도와준다.

CSS Grid Basic

Grid 시스템은 가로 세로를 지정해서 가능하다.

예를 들어보자. 하나의 박스를 가로 30px, 세로 30px 으로 지정해서 만들수 있다.

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .father {
            display: grid;
            grid-template-columns: 30px 50px;
            grid-template-rows: 30px 12px;
        }

        .box {
            background-color: #f1c40f;
        }
    </style>
</head>
<body>
    <div class="father">
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
    </div>
</body>
</html>

1541517517042

Grid 기능

Grid-template-rows, Grid-template-columns

만약 여러개로 만든다고 하면 다음과 같이 가능하다.

  • 30x30
  • 50x30
  • 30x12
  • 50x12
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .father {
            display: grid;
            grid-template-columns: 30px 50px;
            grid-template-rows: 30px 12px;
            grid-gap: 5px;
        }

        .box {
            background-color: #f1c40f;
        }
    </style>
</head>
<body>
    <div class="father">
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
    </div>
</body>
</html>

1541517898405

Grid-auto-rows

grid-template-columns 에 지정된 수를 초과하는 경우 높이를 지정할 수 있다.

  • Grid-auto-columns 반대되는 의미로 사용가능하다.
        .father {
            display: grid;
            grid-template-columns: 30px 50px;
            grid-template-rows: 30px 12px;
            grid-gap: 5px;
            grid-auto-rows: 60px;
        }

1541518352043

Grid-auto-flow

grid-auto-columns 나 grid-auto-rows 사용시 진행되는 배치를 뜻한다. row 나 column 을 지정 가능하다.

flex-direction 과 비슷한 개념이라고 보면 된다.

Grid-template-ares **

문자열로 템플릿을 정한다음 하위 css에 속성을 하나씩 지정해줌

  • 문자열로 템플릿 구성
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .father {
            display: grid;
            grid-auto-rows: 200px;
            grid-gap: 5px;
            grid-template-areas: "header header header"
                "content content sidebar"
                "content content sidebar"
                "footer footer footer"
        }

        .first {
            grid-area: header;
            background-color: #f1c40f;
        }

        .second {
            grid-area: sidebar;
            background-color: #27ae60;
        }

        .third {
            grid-area: footer;
            background-color: #3498db;
        }

        .fourth {
            grid-area: content;
            background-color: #d35400;
        }
    </style>
</head>

<body>
    <div class="father">
        <div class="first"></div>
        <div class="second"></div>
        <div class="third"></div>
        <div class="fourth"></div>
    </div>
</body>

</html>

1541520032893

fr and repeate()

fr

fr = fraction : 가능한 최대 넓이를 가지자

반응형을 제작시 계산이 필요하지 않는 경우가 많다. 예를 들어 분할해서 균등하게 넣고 싶다던지 하는..

그럴경우 fr 로 사용 가능하다.

우선 적용전 화면을 살펴보자.

    <style>
        .father {
            display: grid;
            grid-auto-rows: 200px;
            grid-gap: 5px;            
        }

        .first {            
            background-color: #f1c40f;
        }

        .second {            
            background-color: #27ae60;
        }

        .third {            
            background-color: #3498db;
        }

        .fourth {            
            background-color: #d35400;
        }
    </style

1541553452769

위 내용처럼 배치되어있다고 하자. 만약 1,3번째 컬럼을 2,4번째 컬럼보다 2배로 넓었으면 좋을 경우 어떻게 해야 할까?

  • grid-template-columns: 2fr 1fr 2fr 1fr;
        .father {
            display: grid;
            grid-auto-rows: 200px;
            grid-gap: 5px;          
            grid-template-columns: 2fr 1fr 2fr 1fr;  
        }

1541553628023

이런식으로 2배 넓이를 줄 수 있다.

repeat

반복적으로 fr 로 단위를 줄 수 있다.

.father {
    display: grid;
    grid-auto-rows: 200px;
    grid-gap: 5px;          
    grid-template-columns: repeat(4, 1fr);  
}

1541554030641

부가적으로 fr 을 추가적으로 설정도 가능하다.

...
grid-template-columns: repeat(3, 1fr) 4fr;  
...

1541554168503

minmax,max-content,min-content

minmax

minmax 는 object의 maximum과 minimum 크기를 지정해주는 도구

...
grid-template-columns: minmax(400px, 2fr) repeat(3, 1fr);  
...

줄어들더라도 첫번째 콘테이너는 최소로 유지되는 걸 볼수 있다.

1541554446953

maxcontent

내부에 있는 content를 기반으로 최대 크기를 정하는 게 max-content 의 핵심

grid-template-columns: max-content repeat(3, 1fr);  

1541554740448

mincontent

내부에 있는 content를 최소 크기만큼만 확보하는게 mincontent

grid-template-columns: min-content repeat(3, 1fr);  

1541554937072

auto-fill, auto-fit

auto-fit

우선 여러개의 grid를 만들었다고 가정하자.

<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .father {
            display: grid;            
            grid-gap: 10px;
            grid-auto-rows: 100px;          
            grid-template-columns: repeat(4, 1fr);  
        }

        .box:nth-child(even) {
            background-color: #3498db;            
        }

        .box:nth-child(odd) {
            background-color: #34495e;
        }
    </style>
</head>

<body>
    <div class="father">
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
        <div class="box"></div>
    </div>
</body>

</html>

1541555438098

만약 각각의 아이템을 꽉 차게 배치를 하고 싶다고 하면 어떻게 해야 할까?

그럴때 auto-fit 으로 구성할 수 있다.

grid-template-columns: repeat(auto-fit, 1fr);

1541555562298

grid-template-columns: repeat(auto-fit, 50px);

1541555657104

grid-template-columns: repeat(auto-fit, 340px);	

1541555749328

하지만 공간이 부족할 경우 오른쪽 빨간 박스 처럼 빈공간을 보여주고 하단으로 내려가게 된다.

이걸 방지하기 위해 이전에 공부한 minmax 를 적용하면 채울수 있다.

grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));

1541555927326

auto-fill

콘텐트가 없다고 해도 레이아웃을 다 채울수 있다. ghost-grid로 나머지 부분을 채우는 걸 뜻한다.

grid-template-columns: repeat(auto-fill, minmax(50px, 1fr));  

1541556167553

auto-fit 과 auto-fill 차이점은 ghost-grid 가 있는지 없는지이다.

justify content, Align content, Place content

이전에 배운 Flex 에서 비슷한 속성이 있다.

<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .father {
            display: grid;            
            grid-gap: 10px;
            grid-auto-rows: 100px;          
            grid-template-columns: repeat(5, 100px);
            color: white;
        }

        .box:nth-child(even) {
            background-color: #3498db;            
        }

        .box:nth-child(odd) {
            background-color: #34495e;
        }
    </style>
</head>

<body>
    <div class="father">
        <div class="box">1</div>
        <div class="box">2</div>
        <div class="box">3</div>
        <div class="box">4</div>
        <div class="box">5</div>
    </div>
</body>

</html>

1541556498606

이런 형태가 있다고 하자.

justify content

justify-content: center;

1541556556046

이런식으로 중앙으로 이동을 한다.

start,end 로 설정가능하다.

align content

align-content: center;
height: 100vh;

1541556714026

grid 에서도 justify content 는 가로축을 뜻하며 align conent는 세로축을 뜻한다.

Place content

justify conent 와 align content 두개를 합쳐서 설정할 수 있는 속성이다.

place-content: center end;

1541556905434

즉 첫번째 인자는 align content 가 들어가고 두번째 인자는 justify content 가 들어간다.

justify items, align items, place items

justify items

콘텐트가 아닌 내용을 안에 위치 시킨다.

        .father {
            display: grid;            
            grid-gap: 10px;
            grid-auto-rows: 100px;          
            grid-template-columns: repeat(5, 100px);
            color: white;            
            height: 100vh;
            justify-items: center;
        }

1541557231077

justify-items: center;

1541557284891

주의 할점은 외부적으로 컨테이너가 있고 내부적으로만 움직이는 것이다.

1541557357114

align items

align-items: start;

1541557432232

place items

align items 와 justify items 를 혼합해서 사용 가능하다.

place-items: end center; 

1541557544211

Grid column, Row Start and End

Grid + Flex 혼합해서 사용

<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .father {
            display: grid;            
            grid-gap: 10px;
            grid-auto-rows: 100px;          
            grid-template-columns: repeat(5,1fr);            
        }

        .box {
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
        }

        .box:nth-child(even) {
            background-color: #3498db;            
        }

        .box:nth-child(odd) {
            background-color: #34495e;
        }
    </style>
</head>

<body>
    <div class="father">
        <div class="box">1</div>
        <div class="box">2</div>
        <div class="box">3</div>
        <div class="box">4</div>
        <div class="box">5</div>
        <div class="box">6</div>
        <div class="box">7</div>
        <div class="box">8</div>
        <div class="box">9</div>
        <div class="box">10</div>
        <div class="box">11</div>
        <div class="box">12</div>
        <div class="box">13</div>
        <div class="box">14</div>
        <div class="box">15</div>
    </div>
</body>

</html>

1541557844544

위와 같은 이미지에서 만약 1,2 를 합치고 싶은 경우를 생각해보자.

Grid Columns

Row Start / Row End

        .box:first-child {
            grid-column: 1/3;
        }

1541558064324

대체해서 이런식으로도 사용 가능하다.

.box:first-child {
    grid-column-start: 1;
    grid-column-end: 5;
}

1541558243079

end 에 -1 을 적용시 끝까지 적용된다.

grid-column-end: -1;

1541558400329

Line Naming and Grid-auto-flow

Line naming

이전에 배운 grid-column 에 이름을 붙여서 확장이 가능하다.

    <style>
        .father {
            display: grid;            
            grid-gap: 10px;
            grid-auto-rows: 100px;          
            grid-template-columns: 
                [first-line] 1fr 
                [awesome-line] 1fr 
                [sexy-line] 1fr 
                [third-line] 1fr 
                [fourth-line] 1fr 
                [line-line];
        }

        .box {
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
        }

        .box:nth-child(even) {
            background-color: #3498db;            
        }

        .box:nth-child(odd) {
            background-color: #34495e;
        }

        .box:first-child {
            grid-column-start: sexy-line;
            grid-column-end: fourth-line;
        }
    </style>

sexy-line 부터 fourth-line 까지 확장하는 걸 뜻한다.

1541558839383

추가적으로 repeat 를 붙여서 사용도 가능하다.

        .father {
            display: grid;            
            grid-gap: 10px;
            grid-auto-rows: 100px;          
            grid-template-columns: [first-line] repeat(5, 1fr) [last-line];
                
        }

        .box:first-child {
            grid-column-start: first-line;
            grid-column-end: last-line;
        }

1541559027838

grid-auto-flow

1541558839383

보시다 시피 1 번 앞에 2개가 비어 있다. 이럴경우 밑에 3,4번을 위로 올려서 채우길 원할 수 있다.

이럴경우 grid-auto-flow 를 사용할 수 있다.

grid-auto-flow 를 row dense 으로 채울수 있다.

.father {
        display: grid;            
        grid-gap: 10px;
        grid-auto-rows: 100px;  
        grid-auto-flow: row dense;        
        grid-template-columns: 
        [first-line] 1fr 
        [awesome-line] 1fr 
        [sexy-line] 1fr 
        [third-line] 1fr 
        [fourth-line] 1fr 
        [line-line];
}

1541559293713

Grid Row, Row Start and End

만약 start 와 end 를 넣기에 애매한 경우 span 으로 이용가능하다.

        .box:first-child {
            grid-row: span 3;
            grid-column: span 3;
        }
		

1541559704198

화면을 축소해도 1은 유지를 한다.

1541559759753

Grid Area

Grid Area 로 편하게 위치를 지정 할 수 있다.

row startcolumn startrow endcolumn end

        .box:first-child {
            grid-area: 2 / 1 / 4 / -1;
        }

1541560015694

        .box:first-child {
            grid-area: span 4 / span 4;
        }

1541560095334

justify, align, place self

내부적으로 아이템들을 좀 더 다른 위치로 만들고 싶은 경우 사용한다.

어떤건 왼쪽 위로 올라가고 어떤건 하단 오른쪽으로 이동하게 구성

.box:first-child {
    justify-self: center;
    align-self:center;
}

1541560285423

place-self: center 으로도 똑같이 적용가능하다.

이상으로 CSS 공부한 내용이었다. 다음시간에는 PostCSS 를 배워보도록 하자.

참석해주셔서 감사합니다.

'스터디 > WEB' 카테고리의 다른 글

Sexy Typograping - Day2  (0) 2019.05.19
Css next grid 클론 ( 1 ~ 5 )  (0) 2018.11.17
PostCSS 스터디 정리 ( 2주차 )  (0) 2018.11.12
Web 디자인시 유의 할 점 정리  (0) 2018.11.09
Flex Css 스터디 정리  (0) 2018.11.06

Flutter 왓츠앱 클론

부산에서 매주 진행되는 Flutter 스터디 3주차 내용입니다.

더 많은 부산에서 스터디 정보는 네이버 카페 에서 확인 가능합니다.

소스는 Github 에서 확인 가능합니다.

왓츠앱 클론

오늘은 먼저 UI 개발 시간을 가져볼 예정이다.

우선 완성된 화면은 다음과 같다.


ChatScreen

화면은 크게 2개로 나뉜다. 리스트뷰와 하단에 텍스트입력창이다.

그리고 하단에 텍스트가 입력이 될 때마다 리스트가 업데이트 되는 구조이기 때문에 StatefuleWidget 으로 간다.

class ChatScreen extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => ChatState();
}

//채팅 화면
class ChatState extends State<ChatScreen>{
    @override
  Widget build(BuildContext context) {
      ....
  }
}

앞서 설명했다 싶이 크게 두 부분으로 나뉘어 진다.

리스트뷰와 텍스트 입력창

  • Column 은 세로로 정렬 해준다.
  • _buildListItems은 추후 리스트뷰를 만들어 준다.
  • Stack 으로 뒤에 배경을 깔아주고 위에 리스트뷰로 올린다.
  • _buildBottomBar은 하단 텍스트 위젯을 만들는 함수이다.
@override
  Widget build(BuildContext context) {
    return Container(
      child: Column(
        children: <Widget>[
          Flexible(
            child:Stack(
              children: <Widget>[
                Positioned(
                  top: 0.0,
                  child: Image.asset(
                    "assets/images/bg_whatsapp.png",
                    fit: BoxFit.fill,
                  ),
                ),
                _buildListItems()
              ],
            ),
          ),
          Divider(height: 1.0,),
          Container(
            decoration:
              BoxDecoration(color: Theme.of(context).cardColor),
            child: _buildBottomBar(),
          )
        ],
      ),
    );
  }

리스트 뷰는 ListView.builder 로 구성해서 만들어준다.

  Widget _buildListItems() {
    return ListView.builder(
        itemCount: items.length,
        itemBuilder: (BuildContext context, int index) => _generateItems(index)
    );
  }
...
    _generateItems(int index) {
    	return items[index];
	}

하단 바텀 뷰는 세가지 위젯으로 이루어져있다.

  • 사진 아이콘 (현재는 채팅아이콘)
  • 텍스트 입력창
  • 전송버튼

Flexible 은 남은 공간을 꽉 차게 만들어 준다.

그래서 아이콘 2개를 각각 왼쪽, 오른쪽에 위치해준다. 그리고 중간을 Flexible 로 꽉 채워준다.

  • 텍스트폼 내용은 TextEditingController 를 통해서 가져오고 설정할 수 있다.
Widget _buildBottomBar() {
    return IconTheme(
      data: IconThemeData(
        color: Theme.of(context).accentColor
      ),
      child: Container(
        color: Colors.black87,
        child: Row(
          children: <Widget>[
            Container(
              margin: EdgeInsets.symmetric(horizontal: 4.0),
              child: IconButton(
                  icon: Icon(
                      Theme.of(context).accentColor
                  ),
                  onPressed: () {
                  ...
                  }
              ),
            ),
            Flexible(
              child: TextField(
                controller: _tec,
                style: TextStyle(color: Colors.white),
                decoration: InputDecoration(
                  border: InputBorder.none,
                ),
              ),
            ),
            Container(
              margin: EdgeInsets.symmetric(horizontal: 4.0),
              child: _getDefaultSendButton(),
            )
          ],
        ),
      ),
    );
  }

이제 전송 버튼을 클릭시 대화 내용을 Item 이라는 리스트에 추가해준다. setState 를 통해야 하는 건 기본!!

List<Bubble> items = [];
...
setState(() {
    items.add(
        Bubble(
            message: _tec.text,
            time: _getCurrentTime(),
            delivered: true,
            isYours: !_isMe,
        ),
    );
    _tec.text = "";
});

전체 소스

import 'package:flutter/material.dart';
import 'package:youtube_clone_app/src/widgets/Bubble.dart';
import 'package:intl/intl.dart';

class ChatScreen extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => ChatState();
}

//채팅 화면
class ChatState extends State<ChatScreen>{

  List<Bubble> items = [];
  TextEditingController _tec = TextEditingController();

  bool _isMe = true;

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Column(
        children: <Widget>[
          Flexible(
            child:Stack(
              children: <Widget>[
                Positioned(
                  top: 0.0,
                  child: Image.asset(
                    "assets/images/bg_whatsapp.png",
                    fit: BoxFit.fill,
                  ),
                ),
                _buildListItems()
              ],
            ),
          ),
          Divider(height: 1.0,),
          Container(
            decoration:
              BoxDecoration(color: Theme.of(context).cardColor),
            child: _buildBottomBar(),
          )
        ],
      ),
    );
  }

  Widget _buildListItems() {
    return ListView.builder(
        itemCount: items.length,
        itemBuilder: (BuildContext context, int index) => _generateItems(index)
    );
  }

  Widget _buildBottomBar() {
    return IconTheme(
      data: IconThemeData(
        color: Theme.of(context).accentColor
      ),
      child: Container(
        color: Colors.black87,
        child: Row(
          children: <Widget>[
            Container(
              margin: EdgeInsets.symmetric(horizontal: 4.0),
              child: IconButton(
                  icon: Icon(
                      Icons.chat,
                      color: _isMe
                        ? Theme.of(context).accentColor
                        : Colors.white
                  ),
                  onPressed: () {
                    setState(() {
                      _isMe = !_isMe;
                    });
                  }
              ),
            ),
            Flexible(
              child: TextField(
                controller: _tec,
                style: TextStyle(color: Colors.white),
                decoration: InputDecoration(
                  border: InputBorder.none,
                ),
              ),
            ),
            Container(
              margin: EdgeInsets.symmetric(horizontal: 4.0),
              child: _getDefaultSendButton(),
            )
          ],
        ),
      ),
    );
  }

  _getDefaultSendButton() {
    return IconButton(
      icon: Icon(Icons.send),
      onPressed: () {
        setState(() {
          items.add(
            Bubble(
              message: _tec.text,
              time: _getCurrentTime(),
              delivered: true,
              isYours: !_isMe,
            ),
          );
          _tec.text = "";
        });
      },
    );
  }

  _generateItems(int index) {
    return items[index];
  }

  _getCurrentTime() {
    final f = new DateFormat('hh:mm');

    return f.format(new DateTime.now());
  }
}

Bubble

풍선 대화말을 보여주는 위젯이다.

기본적으로 내부적으로 변경되는 경우가 없으므로 StatelessWidget 으로 구현한다.

  • message : 대화내용
  • time : 전송시간
  • delivered : 전송여부 (현재로서는 무조건 all done 상태)
  • isYours 은 상대방 메세지인지 체크
class Bubble extends StatelessWidget {
  Bubble({this.message, this.time, this.delivered, this.isYours});
}

풍선 사방에 약간 라운드 처리를 위해 Radius 설정한다.

    final radius = isYours
        ? BorderRadius.only(
      topRight: Radius.circular(5.0),
      bottomLeft: Radius.circular(10.0),
      bottomRight: Radius.circular(5.0),
    )
        : BorderRadius.only(
      topLeft: Radius.circular(5.0),
      bottomLeft: Radius.circular(5.0),
      bottomRight: Radius.circular(10.0),

그림자 효과와 라운딩 효과를 위해 decoration 적용

decoration: BoxDecoration(
    boxShadow: [
    BoxShadow(
    blurRadius: .5,
    spreadRadius: 1.0,
    color: Colors.black.withOpacity(.12))
    ],
    color: bg,
    borderRadius: radius,
),

내부에 텍스트 넣는 부분이다.

  • stack 으로 구성하는데 EdgeInsets.only(right: 48.0)으로 오른쪽에 여백을 둔다.

  • 그리고 그 여백에 날짜와 all done아이콘을 위치 한다.

    1541500802760

		child: Stack(
            children: <Widget>[
              Padding(
                padding: EdgeInsets.only(right: 48.0),
                child: Text(message),
              ),
              Positioned(
                bottom: 0.0,
                right: 0.0,
                child: Row(
                  children: <Widget>[
                    Text(time,
                        style: TextStyle(
                          color: Colors.black38,
                          fontSize: 10.0,
                        )),
                    SizedBox(width: 3.0),
                    Icon(
                      icon,
                      size: 12.0,
                      color: Colors.black38,
                    )
                  ],
                ),
              )
            ],
          ),

이상으로 간단하게 왓츠앱 대화 UI 를 만들어보았다.

다음시간에는 파이어베이스 및 인증 를 연동해서 그룹 대화를 만들도록 해보겠다.

참석해주셔서 감사합니다.

소스는 Github 에서 받으실수 있습니다.


HTML + CSS 스터디 1주차 스터디 정리내용

부산에서 매주 진행되는 CSS 초중급 스터디입니다.

목차

  • Flex

    • Flex Basic

    • Main Axis and Cross Axis

    • Flex Wrap and Direction

      • wrap

      • flex-direction

    • Align self

Flex

Flex Basic

컨테이너들을 가로 또는 세로로 정렬을 쉽게 도와준다. .

  • display

  • justify-content

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <meta http-equiv="X-UA-Compatible" content="ie=edge">
   <title>Document</title>
   <style>
       body{
           display: flex;
           justify-content: space-between;
      }
       .box {
          width: 300px;
          height: 300px;
          background-color: red;
          border: 1px solid white;
      }
   </style>
</head>
<body>    
   <div class="box"></div>
   <div class="box"></div>
   <div class="box"></div>
</body>
</html>

Main Axis and Cross Axis

Main Axisjustify-content, Cross Axisalign-items

Flex 에서는 기본 flex-directionrow (가로) 로 되어 있다.

  • Flex-direaction 가 row 일 경우

  • justify-content 는 가로축

  • align-items는 세로축

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <meta http-equiv="X-UA-Compatible" content="ie=edge">
   <title>Document</title>
   <style>
       .father{
           display: flex;
           flex-direction: column; /* or row */
           justify-content: space-around;
           align-items: center;
           height: 100vh;
      }
       .box {
          width: 300px;
          height: 300px;
          background-color: red;
          border: 1px solid white;
      }
   </style>
</head>
<body>    
   <div class="father">
       <div class="box"></div>
       <div class="box"></div>
       <div class="box"></div>
   </div>
</body>
</html>



Flex Wrap And Direction

wrap

화면이 축소될 경우 레이아웃이 커지면서 차지하게 된다. 즉 늘어나게 되는 것이다.


이걸 방지 하기 위해서 wrap 속성을 추가할 수 있다.

기본 옵션은 nowrap이다.

wrap 적용시 나오는 화면은 아래와 같다.



flex-direction

flex-direction을 row-reverse 로 주는 경우 반대 방향으로 바꿀 수 있다.

그럴 경우 justify-content 도 영향을 주니 참고하자.

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <meta http-equiv="X-UA-Compatible" content="ie=edge">
   <title>Document</title>
   <style>
       .father{
           display: flex;          
           flex-wrap: wrap;
           justify-content: flex-start;            
           height: 100vh;
           background-color: bisque;
           flex-direction: row-reverse
      }
       .box {
          width: 300px;
          height: 300px;
          background-color: red;
          border: 1px solid white;
          color:white;
          font-size: 50px;
          display: flex;
          justify-content: center;
          align-items: center;
      }
   </style>
</head>
<body>    
   <div class="father">
       <div class="box">1</div>
       <div class="box">2</div>
       <div class="box">3</div>
   </div>
</body>
</html>



Align self

chidren 에게 따로 이동을 줄 수 있는 방법 중 하나.

<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <meta http-equiv="X-UA-Compatible" content="ie=edge">
   <title>Document</title>
   <style>
       .father{
           display: flex;          
           flex-wrap: wrap;
           justify-content: flex-start;            
           height: 100vh;
           background-color: bisque;            
      }
       .box {
          width: 300px;
          height: 300px;
          background-color: red;
          border: 1px solid white;
          color:white;
          font-size: 50px;
          display: flex;
          justify-content: center;
          align-items: center;
      }

       .box:first-child {
           align-self: flex-end;
      }
   </style>
</head>
<body>    
   <div class="father">
       <div class="box">1</div>
       <div class="box">2</div>
       <div class="box">3</div>
   </div>
</body>
</html>


도커 & 쿠버네티스 5주차

부산에서 매주 진행되는 스터디입니다.

부산에서 다른 스터디 내용을 보실려면 카페 에서 보실수 있습니다.

https://www.udemy.com/docker-and-kubernetes-the-complete-guide 을 공부하고 있습니다.

1주차 스터디

2주차 스터디

3주차 스터디

4주차 스터디

5주차 스터디 공부 내용

이번 차에서는 좀 더 복잡한 형태의 서비스를 구성해볼 예정이다.

인덱스를 넣으면 해당되는 피보나치 수열을 계산하는 출력해주는 앱을 개발할 예정이다.

화면 구성

화면은 React 로 작성될 예정이며 아래와 같이 구성된다.

  • 인덱스를 입력받는다.

  • Indicies I hava seen 에서는 지금까지 입력받은 인덱스를 저장해서 보여준다. ( by postgres db)

  • 마지막 Worker 라는 로직을 통해서 수열이 계산되어 지고 그 값이 Redis 에 저장되서 보여주게 된다.

서비스 구성


  • Nginx` 서버를 통해서 접근된다.

  • 프론트는 React Server 로 프록시 된다.

  • API 서버는 Express Server 로 프록시 된다.

  • Express ServerRedis 로 값을 호출하지만 있는 경우 바로 리턴이 되겠지만 없는 경우 Worker 를 통해서 계산된 값을 다시 가져와서 저장하고 리턴을 한다.

  • 마지막으로 유저가 입력한 index은 저장을 postgres 통해서 한다.

이후에 진행되는 https://github.com/bear2u/docker-study2 여기에서 받을수 있다.

소스 구성

Worker

피보나치 수열을 계산하는 로직이 담긴 서비스를 개발한다.

  • redis 추가시 구독

  • 새로운 값이 입력되는 경우 fib(index) 함수를 통해서 값을 계산해서 다시 redis 에 저장한다.

keys.js

module.exports = {
 redisHost: process.env.REDIS_HOST,
 redisPort: process.env.REDIS_PORT
};

index.js

const keys = require('./keys');
const redis = require('redis');

const redisClient = redis.createClient({
 host: keys.redisHost,
 port: keys.redisPort,
 retry_strategy: () => 1000
});
const sub = redisClient.duplicate();

function fib(index) {
 if (index < 2) return 1;
 return fib(index - 1) + fib(index - 2);
}

sub.on('message', (channel, message) => {
 redisClient.hset('values', message, fib(parseInt(message)));
});
sub.subscribe('insert');

package.json

{
 "dependencies": {
   "nodemon": "1.18.3",
   "redis": "2.8.0"
},
 "scripts": {
   "start": "node index.js",
   "dev": "nodemon"
}
}

Server

keys.js

  • 설정값은 추후 도커 환경변수로 입력받게 된다.

module.exports = {
 redisHost: process.env.REDIS_HOST,
 redisPort: process.env.REDIS_PORT,
 pgUser: process.env.PGUSER,
 pgHost: process.env.PGHOST,
 pgDatabase: process.env.PGDATABASE,
 pgPassword: process.env.PGPASSWORD,
 pgPort: process.env.PGPORT
};

index.js

  • express 서버 사용

  • postgres 호출

  • redis 호출

  • api 호출 따른 restful 작성

const keys = require('./keys');

// Express App Setup
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');

const app = express();
app.use(cors());
app.use(bodyParser.json());

// Postgres Client Setup
const { Pool } = require('pg');
const pgClient = new Pool({
 user: keys.pgUser,
 host: keys.pgHost,
 database: keys.pgDatabase,
 password: keys.pgPassword,
 port: keys.pgPort
});
pgClient.on('error', () => console.log('Lost PG connection'));

pgClient
.query('CREATE TABLE IF NOT EXISTS values (number INT)')
.catch(err => console.log(err));

// Redis Client Setup
const redis = require('redis');
const redisClient = redis.createClient({
 host: keys.redisHost,
 port: keys.redisPort,
 retry_strategy: () => 1000
});
const redisPublisher = redisClient.duplicate();

// Express route handlers

app.get('/', (req, res) => {
 res.send('Hi');
});

app.get('/values/all', async (req, res) => {
 const values = await pgClient.query('SELECT * from values');

 res.send(values.rows);
});

app.get('/values/current', async (req, res) => {
 redisClient.hgetall('values', (err, values) => {
   res.send(values);
});
});

app.post('/values', async (req, res) => {
 const index = req.body.index;

 if (parseInt(index) > 40) {
   return res.status(422).send('Index too high');
}

 redisClient.hset('values', index, 'Nothing yet!');
 redisPublisher.publish('insert', index);
 pgClient.query('INSERT INTO values(number) VALUES($1)', [index]);

 res.send({ working: true });
});

app.listen(5000, err => {
 console.log('Listening');
});

package.json

{
 "dependencies": {
   "express": "4.16.3",
   "pg": "7.4.3",
   "redis": "2.8.0",
   "cors": "2.8.4",
   "nodemon": "1.18.3",
   "body-parser": "*"
},
 "scripts": {
   "dev": "nodemon",
   "start": "node index.js"
}
}

Client

  • Routing 을 통해서 OtherPage.js 호출

  • react-create-app 을 통해서 설치

  • Fib.js, App.js, Otherpage.js 참고

Fib.js

import React, { Component } from 'react';
import axios from 'axios';

class Fib extends Component {
 state = {
   seenIndexes: [],
   values: {},
   index: ''
};

 componentDidMount() {
   this.fetchValues();
   this.fetchIndexes();
}

 async fetchValues() {
   const values = await axios.get('/api/values/current');
   this.setState({ values: values.data });
}

 async fetchIndexes() {
   const seenIndexes = await axios.get('/api/values/all');
   this.setState({
     seenIndexes: seenIndexes.data
  });
}

 handleSubmit = async event => {
   event.preventDefault();

   await axios.post('/api/values', {
     index: this.state.index
  });
   this.setState({ index: '' });
};

 renderSeenIndexes() {
   return this.state.seenIndexes.map(({ number }) => number).join(', ');
}

 renderValues() {
   const entries = [];

   for (let key in this.state.values) {
     entries.push(
       <div key={key}>
         For index {key} I calculated {this.state.values[key]}
       </div>
    );
  }

   return entries;
}

 render() {
   return (
     <div>
       <form onSubmit={this.handleSubmit}>
         <label>Enter your index:</label>
         <input
           value={this.state.index}
           onChange={event => this.setState({ index: event.target.value })}
         />
         <button>Submit</button>
       </form>

       <h3>Indexes I have seen:</h3>
      {this.renderSeenIndexes()}

       <h3>Calculated Values:</h3>
      {this.renderValues()}
     </div>
  );
}
}

export default Fib;

OtherPage.js

import React from 'react';
import { Link } from 'react-router-dom';

export default () => {
 return (
   <div>
     Im some other page
     <Link to="/">Go back to home page!</Link>
   </div>
);
};

App.js

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
import OtherPage from './OtherPage';
import Fib from './Fib';

class App extends Component {
 render() {
   return (
     <Router>
       <div className="App">
         <header className="App-header">
           <img src={logo} className="App-logo" alt="logo" />
           <h1 className="App-title">Welcome to React</h1>
           <Link to="/">Home</Link>
           <Link to="/otherpage">Other Page</Link>
         </header>
         <div>
           <Route exact path="/" component={Fib} />
           <Route path="/otherpage" component={OtherPage} />
         </div>
       </div>
     </Router>
  );
}
}

export default App;

위의 모든 소스는 여기에서 받을수 있다.

자 그럼 실제 이러한 서비스들을 각각의 도커로 만들어서 묶어서 연결하는지를 공부해도록 하자. 기대되는 순간이 아닐수 없다.

도커 구성

Complex 폴더

자 그럼 이제 도커 세팅을 해보자. 우선 기본 환경은 세개의 폴더로 구성된 루트에서 작업이 시작될 예정이다.

complex 폴더

Client Dockerfile.dev

Client > Dockerfile.dev

FROM node:alpine
WORKDIR '/app'
COPY './package.json' ./
RUN npm install
COPY . .
CMD ["npm", "run", "start"]

이게 낯설다면 이전 주차들을 다시 공부하도록 하자.

> docker build -f Dockerfile.dev .
....
> docker run 도커 아이디

Server Dockerfile.dev

Server > Dockerfile.dev

FROM node:alpine
WORKDIR "/app"
COPY ./package.json ./
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]
> docker build -f Dockerfile.dev .
....
> docker run 도커 아이디

커넥션 오류가 나올것이다. 아직 DB를 올린게 아니라서 그렇다.

Worker

workder > Dockerfile.dev

FROM node:alpine
WORKDIR "/app"
COPY ./package.json ./
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]

Dcoker-Compose

우리가 구성할 건 다음과 같은 형태를 지닌다.

그러면 하나씩 구성해보도록 하자. 마지막에 설정한 환경변수는 각각의 폴더내 keys.js 에 들어갈 내용들이다.

module.exports = {
 redisHost: process.env.REDIS_HOST,
 redisPort: process.env.REDIS_PORT,
 pgUser: process.env.PGUSER,
 pgHost: process.env.PGHOST,
 pgDatabase: process.env.PGDATABASE,
 pgPassword: process.env.PGPASSWORD,
 pgPort: process.env.PGPORT
};

docker-compose.yml

dockerfile들을 한번에 순서대로 만들기 위한 통합파일을 만들어준다. 위치는 root 에서 만든다.

docker-compose.yml

  • Postgres

    version: '3'
    services:
    postgres:
      image: 'postgres:latest'
    > docker-compose up

  • redis

    redis:
      image: 'redis:latest'
    docker-compose up

  • server

    • Specify build

      build:
        dockerfile: Dockerfile.dev
        context: ./server
    • Specify volumes

      volumes:
          - /app/node_modules
          - ./server:/app
    • Specify env variables

      • 환경 설정시 variableName 지정을 안 하는 경우 현재 시스템에 있는 변수로 적용된다.

          # Specify env variables
        environment:
          - REDIS_HOST:redis
          - REDIS_PORT:6379
          - PGUSER:postgres
          - PGHOST:postgres
          - PGDATABASE:postgres
          - PGPASSWORD:postgres_password
          - PGPORT:5432
  • Client

      client:
      build:
        dockerfile: Dockerfile.dev
        context: ./client
      volumes:
        - /app/node_modules
        - ./client:/app  
  • worker

      worker:
      build:
        dockerfile: Dockerfile.dev
        context: ./client    
      volumes:
        - /app/node_modules
        - ./worker:/app

전체 소스는 다음과 같다.

docker-compose.yml

version: '3'
services:
postgres:
  image: 'postgres:latest'
redis:
  image: 'redis:latest'  
server:
  # Specify build  
  build:
    dockerfile: Dockerfile.dev
    context: ./server      
  # Specify volumes
  volumes:
    - /app/node_modules
    - ./server:/app
  # Specify env variables
  environment:
    - REDIS_HOST:redis
    - REDIS_PORT:6379
    - PGUSER:postgres
    - PGHOST:postgres
    - PGDATABASE:postgres
    - PGPASSWORD:postgres_password
    - PGPORT:5432
client:
  build:
    dockerfile: Dockerfile.dev
    context: ./client    
  volumes:
    - /app/node_modules
    - ./client:/app    
worker:
  build:
    dockerfile: Dockerfile.dev
    context: ./client    
  volumes:
    - /app/node_modules
    - ./worker:/app      

nginx

Proxy 설정을 해서 프론트와 백단을 분리를 해보자.

nginx docker 이미지에 기본 설정을 clientserver 를 추가해서 proxy 해주도록 하자.

nginx/default.conf


upstream client {
  server client:3000;
}

upstream server {
  server server:5000;
}

server {
  listen 80;

  location / {
      proxy_pass http://client;
  }

  location /api {
      rewrite /api/(.*) /$1 break;
      proxy_pass http://server;
  }
}
  • rewrite /api/(.*) /$1 break;/api 로 들어오는 경우 내부에서 다시 / 로변경해주기 위함

  • client 는 3000 포트로 내부에서 proxy 되며

  • server 는 5000 포트로 내부에서 proxy 된다.

  • 외부에서는 80 포트만으로 제어한다.

DockerFile.dev 생성

nginx/Dockerfile.dev

FROM nginx
COPY ./default.conf /etc/nginx/conf.d/default.conf
  • nginx 도커 이미지를 가져온다.

  • 기본 설정 default.conf 파일을 도커 이미지내 설정으로 덮어쓴다.

docker-compose 에 nginx 추가

  • nginx 폴더내 Dockerfile.dev 를 참조해서 생성한다.

  • localhost는 3050으로 설정하고 nginx 도커는 80 으로 바인딩 해준다.

  • 문제가 생길 경우 자동으로 재실행을 해준다.

  nginx:
  restart: always
  build:
    dockerfile: Dockerfile.dev
    context: ./nginx  
  ports:
    - '3050:80'  

docker-compose 실행

이제 잘되는지 실행을 해보자. 모든 도커 파일을 다시 만들도록 하자.

docker-compose up --build

서버가 정상적으로 실행된 것 확인하기 위해선 진입점을 확인해야 한다.

이전에 nginx 서버는 3050포트와 바인딩된 상태이다.

    ports:
    - '3050:80'

그럼 이제 확인해본다.

http://localhost:3050

React 서버 실행

정상적으로 React 서버가 뜨는 걸 볼수 있다.

하지만 개발자 도구에 콘솔을 열어보면 웹소켓 문제가 보인다.

실시간 연동이 안될 뿐이지 다시 새로고침을 해보면 정상적으로 저장된 걸 볼수 있다.

  • 5번째 피보나치 수는 8이다.

그러면 nginx 서버에서 웹소켓을 설정해서 실시간 반영이 되도록 해보자.

nginx>default.conf

location /sockjs-node {
  proxy_pass http://client;
  proxy_http_version 1.1;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection "Upgrade";
}

Dockerfile 을 새로 빌드해서 up 을 해보자.

정상적으로 보여지는 걸 볼수 있다.

문제점

  1. 만약 submit 을 했는데 실시간으로 변경이 안되는 경우

client/Fib.js 에 다음과 같은 코드를 넣어야 한다.

componentDidMount() {
setInterval(() => {
  this.fetchValues();
  this.fetchIndexes();
}, 1000)
}
  1. docker-compose up 을 할때 오류가 나면 다시 up 을 해주자.

  2. window에서 종종 오류가 나기 때문에 mac이나 리눅스에서 하길 추천한다.

여기까지 소스는 Github 에서 받을 수 있다.

이상으로 5주차 도커&쿠버네이트 수업 정리를 마치도록 한다.

첨부파일

app-release-1.0.apk


기록하는 습관을 가지자!!

https://www.youtube.com/watch?v=Qvjw-5d9oEA


다 적자  ( 마켓에 업로드 예정 )

앱제목 : 다 적자
앱내용 : 기록하는 노트앱
기능
1. 인증방에 편하게 복사할 수 있게끔 하는 유틸성 기능
2. 사진 포함해서 간단한 기록
목적
1. 식사하면서 뭐 먹었는지 기록하기
2. 운동 일지 기록하기
3. 15분방에 인증할때 쉽게 이전 기록 복사해서 붙여넣을 수 있도록 만들기
4. 1시간단위로 기록하는 습관 가지기

개발 플랫폼 : 
Flutter / Android, IOS 을 한꺼번에 출시가능한 하이브리드 플랫폼





1차 전체 로직



스크린샷


이제 15분방에서 열심히 인증하자!!




+ Recent posts