반응형

서버에 요청을 보낼 때는 주소를 통해 요청의 내용을 표현합니다.

주소가 /index.html 이면 서버의 index.html을 보내달라는 뜻이고, /about.html 이면 about.html을 보내달라는 뜻입니다.

 

그리고 항상 html 만 요청할 필요는 없습니다. css나 js 그리고 이미지 같은 파일을 요청할 수도 있고, 특정 동작을 행하는 것을 요청할 수 도 있습니다. 요청의 내용이 주소를 통해 표현되므로 서버가 이해하기 쉬운 주소를 사용하는 것이 좋습니다. 여기서 REST라는 개념이 등장합니다.

 

REST(Representational State Transfer)는 웹 기반 시스템에서 자원을 정의하고, 그 자원에 대한 상태를 주고 받는 방법을 기술한 아키텍처 스타일입니다. REST는 클라이언트와 서버 간의 통신을 위한 규칙을 제공하여 분산 시스템을 구축하는 데 사용됩니다.

  1. 자원(Resource) 지향: 모든 자원에는 고유한 식별자(URI) 있으며, 클라이언트는 이를 통해 자원을 식별하고 조작합니다. 예를 들어, 페이지의 URI 해당 자원의 위치를 나타내며 클라이언트는 이를 통해 해당 페이지를 요청할 있습니다.
  2. 상태를 주고받는 구조(Stateful): REST 상태를 저장하지 않고, 요청 간의 상태 전환을 통해 데이터를 주고 받습니다. 이는 서버가 클라이언트의 상태를 관리하지 않고, 요청이 독립적으로 처리되도록 합니다.
  3. 표준화된 인터페이스: REST 자원에 대한 표준화된 인터페이스를 제공합니다. 주요 HTTP 메서드(GET, POST, PUT, DELETE) 사용하여 자원을 조작하고, HTTP 상태 코드를 통해 요청의 성공 또는 실패를 알려줍니다.
  4. 계층화(Layered): REST 아키텍처는 서버와 클라이언트 사이에 중간 계층을 두어 구조를 단순화하고 확장성을 향상시킵니다. 이는 서버와 클라이언트 간의 결합도를 줄이고, 시스템의 확장성과 보안성을 향상시킵니다.

REST에서는 주소 외에도 HTTP 요청 메서드라는 것을 사용합니다.

REST에서 사용되는 주요 HTTP 메서드는 다음과 같습니다.

  1. GET: 리소스의 조회를 요청할 때 사용됩니다. 서버로부터 데이터를 가져오는 요청에 사용되며, 요청한 URI의 정보를 반환합니다. GET 요청은 클라이언트가 서버에게 어떤 리소스의 상태를 조회하기 위해 사용됩니다.
  2. POST: 리소스를 생성하기 위해 사용됩니다. 클라이언트가 서버에 새로운 데이터를 제출할 때 사용되며, 서버는 해당 데이터를 처리하고 새로운 리소스를 생성합니다. 예를 들어, 사용자가 폼을 제출하여 새로운 블로그 게시물을 작성하는 경우 POST 요청을 사용할 수 있습니다.
  3. PUT: 리소스의 전체적인 업데이트를 요청할 때 사용됩니다. 클라이언트가 서버에게 리소스의 전체적인 업데이트를 요청하고자 할 때 사용됩니다. 즉, 클라이언트가 요청한 리소스의 모든 내용을 제공하여 서버에 새로운 내용으로 업데이트합니다.
  4. DELETE: 리소스의 삭제를 요청할 때 사용됩니다. 클라이언트가 서버에게 특정 리소스를 삭제하도록 요청하는 메서드입니다. 서버는 해당 리소스를 삭제하고 삭제에 성공했는지에 대한 상태 코드를 반환합니다.
  5. PATCH: 리소스의 일부를 업데이트하기 위해 사용됩니다. PUT과 유사하지만, PATCH는 리소스의 일부분만을 업데이트할 때 사용됩니다. 즉, 클라이언트가 리소스의 일부만을 제공하여 해당 부분을 업데이트하도록 요청합니다.

 

💡 실전예제

프로젝트를 아래와 같이 구성하고 각 파일마다 코드를 아래와 같이 작성합니다.

📘 restFront.css

* CSS 부분과 HTML 부분은 중요하지 않으므로 복사해서 사용하는 것을 권장합니다.

body {
    font-family: Arial, sans-serif;
    background-color: #f4f4f4;
    margin: 0;
    padding: 0;
  }
  
  nav {
    background-color: #333;
    color: #fff;
    padding: 10px;
  }
  
  nav a {
    color: #fff;
    text-decoration: none;
    margin-right: 10px;
  }
  
  nav a:hover {
    text-decoration: underline;
  }
  
  div {
    max-width: 600px;
    margin: 20px auto;
    padding: 20px;
    background-color: #fff;
    border-radius: 5px;
    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
  }
  
  form {
    display: flex;
    margin-bottom: 20px;
  }
  
  input[type="text"] {
    flex: 1;
    padding: 10px;
    border: 1px solid #ccc;
    border-radius: 5px;
    font-size: 16px;
  }
  
  button[type="submit"] {
    padding: 10px 20px;
    background-color: #333;
    color: #fff;
    border: none;
    border-radius: 5px;
    cursor: pointer;
  }
  
  button[type="submit"]:hover {
    background-color: #555;
  }
  
  #list {
    border-top: 1px solid #ccc;
    padding-top: 20px;
  }

 

📘 restFront.html

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="utf-8" />
  <title>RESTful 서버 예제</title>
  <link rel="stylesheet" href="./restFront.css" />
</head>
<body>
<nav>
  <a href="/">Home</a>
  <a href="/about">About</a>
</nav>
<div>
  <form id="form">
    <input type="text" id="username">
    <button type="submit">등록</button>
  </form>
</div>
<div id="list"></div>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="./restFront.js"></script>
</body>
</html>

 

📘 about.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>RESTful 서버 예제</title>
  <link rel="stylesheet" href="./restFront.css" />
</head>
<body>
<nav>
  <a href="/">Home</a>
  <a href="/about">About</a>
</nav>
<div>
  <h2>소개 페이지입니다.</h2>
  <p>사용자 이름을 등록하세요!</p>
</div>
</body>
</html>

 

📘 restFront.js

async function getUser() { // 로딩 시 사용자 정보를 가져오는 함수
    try {
        const res = await axios.get('/users');
        const users = res.data;
        const list = document.getElementById('list');
        list.innerHTML = '';

        // 사용자마다 반복적으로 화면 표시 및 이벤트 연결
        Object.keys(users).map(function (key) {
            const userDiv = document.createElement('div');
            const span = document.createElement('span');
            span.textContent = users[key];
            const edit = document.createElement('button');
            edit.textContent = '수정';
            edit.addEventListener('click', async () => { // 수정버튼 클릭
                const name = prompt('바꿀 이름을 입력하세요');
                if (!name) {
                    return alert('이름을 반드시 입력하여야 합니다.');
                }
                try {
                    await axios.put('/user/' + key, { name });
                    getUser();
                } catch (e) {
                    console.error(e);
                }
            });
            const remove = document.createElement('button');
            remove.textContent = '삭제';
            remove.addEventListener('click', async () => { // 삭제 버튼 클릭
                try {
                    await axios.delete('/user/' + key);
                    getUser();
                } catch (e) {
                    console.error(e);
                }
            });
            userDiv.appendChild(span);
            userDiv.appendChild(edit);
            userDiv.appendChild(remove);
            list.appendChild(userDiv);
            console.log(res.data);
        });
    } catch (e) {
        console.error(e);
    }
}

window.onload = getUser; // 화면 로딩 시 getUser 호출
// 폼 제출(submit) 시 실행하기
document.getElementById('form').addEventListener('submit', async (e) => {
    e.preventDefault();
    const name = e.target.username.value;
    if (!name) {
        return alert('이름을 입력하세요');
    }
    try {
        await axios.post('/user', { name });
        getUser();
    } catch (e) {
        console.error(e);
    }
    e.target.username.value = '';
});

 

📘 restServer.js (핵심 부분)

  • req.method로 HTTP 요청 메서드를 구분하고 있습니다.
  • 메서드가 GET 이면 다시 req.url로 요청 주소를 구분합니다.
  • 주소가 '/' 일 때는 restFront.html을 제공하고, 주소가 '/about' 이면 about.html 파일을 제공합니다.
  • 만약 존재하지 않는 파일을 요청했거나 GET 메서드 요청이 아닌 경우라면 404 not found 에러가 응답으로 전송됩니다.
  • 데이터베이스 대용으로 users 라는 객체를 선언해 사용자 정보를 저장하였습니다.
  • POST와 PUT 요청을 처리할 때에는 req.on('data')와 req.on('end')의 사용인데 요청의 본문에 들어 있는 데이터를 꺼내기 위한 작업이라고 볼 수 있습니다.
  • req와 res도 내부적으로는 스트림(readStream과 writeStream)으로 되어 있으므로 요청/응답의 데이터가 스트림 형식으로 전달됩니다.
const http = require('http');
const fs = require('fs').promises;
const path = require('path');

const users = {}; // 데이터 저장용

http.createServer(async (req, res) => {
    try {
        console.log(req.method, req.url);
        if(req.method === 'GET') {
            if(req.url === '/') {
                const data = await fs.readFile(path.join(__dirname, 'restFront.html'));
                res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
                return res.end(data);
            } else if (req.url === '/about') {
                const data = await fs.readFile(path.join(__dirname, 'about.html'));
                res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
                return res.end(data);
            } else if (req.url === '/users') {
                res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8'});
                return res.end(JSON.stringify(users));
            }
            // 주소가 / 도 /about 도 아니면?
            try {
                const data = await fs.readFile(path.join(__dirname, req.url));
                return res.end(data);
            } catch (e) {
                // 주소에 해당하는 라우트를 찾지 못했다는 404 not found error 발생
            }
        } else if (req.method === 'POST') {
            if(req.url === '/user') {
                let body = '';
                // 요청의 body를 stream 형식으로 받음
                req.on('data', (data) => {
                    body += data;
                });
                // 요청의 body를 다 받은 후 실행됨
                return req.on('end', () => {
                    console.log('POST 본문(Body) : ', body);
                    const { name } = JSON.parse(body);
                    const id = Date.now();
                    users[id] = name;
                    res.writeHead(201, { 'Content-Type': 'text/plain; charset=utf-8'});
                    res.end('등록 성공');
                });
            }
        } else if (req.method === 'PUT') {
            if (req.url.startsWith('/user/')) {
                const key = req.url.split('/')[2];
                let body = '';
                req.on('data', (data) => {
                    body += data;
                });
                return req.on('end', () => {
                    console.log('PUT 본문(Body):', body);
                    users[key] = JSON.parse(body).name;
                    res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8'});
                    return res.end(JSON.stringify(users));
                });
            }
        } else if (req.method === 'DELETE') {
            if (req.url.startsWith('/user/')) {
                const key = req.url.split('/')[2];
                delete users[key];
                res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8'});
                return res.end(JSON.stringify(users));
            }
        }
        res.writeHead(404);
        return res.end('NOT FOUND');
    } catch (e) {
        console.error(e);
        res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
        res.end(e.message);
    }
})
.listen(8082, () => {
    console.log('8082번 포트에서 서버 대기중 입니다.');
});

 

💡 서버 실행하기

프로젝트 파일이 있는 폴더에서 터미널을 실행하고 'node restServer' 명령어를 입력하여 서버를 실행합니다.

http://localhost:8082로 접속하면 아래와 같은 화면이 보입니다.

 

home 화면일 경우
about 페이지일 경우
user 등록(POST)

 

user 수정 (PUT)
user 삭제 (DELETE)

 

반응형

'Node.js' 카테고리의 다른 글

REPL  (0) 2024.05.06
반응형

💡 REPL 이란 무엇일까?

자바스크립트는 스크립트 언어입니다. 미리 컴파일하지 않아도 바로 코드를 실행할 수 있습니다.

노드에서도 비슷한 콘솔을 제공하는데요, 입력한 코드를 읽고(read), 해석하고(eval), 반환하고(print), 종료할 때까지 반복한다(loop)라고 해서 REPL(Read Eval Print Loop)이라고 합니다.

 

💡 노드의 REPL을 사용해보자!

그럼 노드의 REPL을 사용해볼까요?

윈도우에서는 명령 프롬프트, 맥이나 리눅스의 경우에는 터미널을 열고 node를 입력합니다.

 

이렇게 node를 입력하면 콘솔창 모양이 > 으로 바뀌게 됩니다. 여기에서 자바스크립트 코드를 입력할 수 있습니다.

간단한 예제코드를 입력해보겠습니다.

 

 

위와 같이 출력이된다면 성공입니다. 

입력한 코드를 읽고, 해석한 뒤 바로 결과물을 출력했습니다. 그리고 종료되기 전까지 입력을 기다립니다.

REPL을 종료하려면 Ctrl + C 를 두 번 누르거나, .exit를 입력하여 종료할 수 있습니다.

 

REPL은 한 두줄 정도의 코드를 테스트하기에는 좋지만, 여러 줄의 코드를 실행하기에는 불편합니다. 긴 코드의 경우에는 자바스크립트 파일을 생성한 후 파일을 통째로 실행하는 것이 편리합니다.

 

💡 자바스크립트 파일을 실행해보기

REPL에 직접 코드를 입력하는 대신에 자바스크립트 파일을 만들어 실행해보겠습니다.
저는 node라는 폴더 안에 자바스크립트 파일을 생성하였습니다.

 

function helloWorld() {
    console.log("Hello World");
    helloNode();
}

function helloNode() {
    console.log("Hello Node");
}

helloWorld();

 

그럼 이제 파일을 실행해 볼까요?

 

js파일이 있는 폴더에서 터미널을 실행하여 node (자바스크립트 파일 경로)를 입력하여 실행합니다. 확장자는 생략가능합니다.

여기에서 주의해야 할 점은 REPL에서 실행하는 것이 아닌 콘솔에서 실행한다는 점입니다.

콘솔에서 REPL로 들어가는 명령어가 node이고, 노드를 통해 파일을 실행하는 명령어가 node (자바스크립트 파일 경로) 입니다.

 

 

 

반응형

'Node.js' 카테고리의 다른 글

Node.js REST와 라우팅 사용하기  (0) 2024.05.06
반응형

위젯은 자식을 하나만 갖는 위젯과 자식을 여러개를 갖는 위젯으로 나뉩니다.
 
자식을 하나만 갖는 대표적인 위젯들은 아래와 같고, 대체로 child 매개변수를 입력받습니다.
 

  • Container 위젯 : 자식을 담는 컨테이너 역할을 합니다. 다만 단순하게 자식을 담는 역할을 하는게 아니라 배경색, 너비와 높이, 테두리 등의 디자인을 지정할 수 있습니다.
  • GestureDetector 위젯 : 플러터에서 제공하는 제스처 기능을 자식 위젯에서 인식하는 위젯, 탭이나 드래그 그리고 더블 클릭 같은 제스처 기능이 자식 위젯에 인식됐을 때 함수를 실행할 수 있습니다.
  • SizedBox 위젯 : 높이와 너비를 지정하는 위젯, Container 위젯과 다르게 디자인적 요소는 적용할 수 없고, const 생성자로 선언할 수 있어서 퍼포먼스 측면에서 더 효율적

 
다수의 자식을 입력할 수 있는 위젯은 children 매개변수를 입력받으며 리스트로 여러 위젯을 입력할 수 있습니다.
 

  • Column 위젯 : children 매개변수에 입력된 모든 위젯들을 세로로 배치
  • Row 위젯 : children 매개변수에 입력된 모두 위젯들을 가로로 배치
  • ListView 위젯 : 리스트를 구현할 때 사용, children 매개변수에 다수의 위젯을 입력할 수 있으며 입력된 위젯이 화면을 벗어나게 되면 스크롤이 가능

 

Children 와 Child의 차이점

child는 위젯을 하나만 추가할 수 있고, children은 여럿을 추가할 수 있습니다.
대부분의 위젯은 child 또는 children 매개변수를 하나만 제공합니다. child와 children 매개변수를 동시에 입력받는 위젯은 존재하지 않습니다.
 
 

제스처 관련 위젯

사용자가 키보드로 글자를 입력하는 행위 이외 모든 입력을 플러터에서는 제스처라고 부릅니다.
화면을 한 번 탭하거나, 두 번 탭하거나, 길게 누르는 행동 모두가 제스처 입니다.
GestureDetector 위젯은 모든 제스처를 매개변수로 제공해줍니다. 제스처 관련 위젯은 하위 위젯에 탭이나 드래그처럼 특정 제스처가 입력됬을 때 인지하고 콜백 함수를 실행합니다.
 

Button 위젯

머터리얼 패키지에서 기본 제공하는 버튼으로 TextButton, OutlinedButton, ElevatedButton이 있습니다. 이 세 개의 버튼은 모두 버튼을 누르면 색깔이 변경되는 리플 효과를 지원합니다.
 
🔷 TextButton

import 'package:flutter/material.dart';

void main(){
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: TextButton(
            // 클릭시 실행
            onPressed: (){
              print('버튼 눌림');
            },

            // 스타일 지정하기
            style: TextButton.styleFrom(
              // 주 색상 지정
              foregroundColor: Colors.red,
            ),
            child: Text('텍스트 버벝'),
          ),
        ),
      ),
    );
  }
}

 

 
 
🔷 OutlinedButton

import 'package:flutter/material.dart';

void main(){
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: OutlinedButton(
            // 클릭시 실행
            onPressed: (){
              print('버튼 눌림');
            },

            // 스타일 지정하기
            style: OutlinedButton.styleFrom(
              // 주 색상 지정
              foregroundColor: Colors.red,
            ),
            child: Text('아웃라인 버튼'),
          ),
        ),
      ),
    );
  }
}

 

 
 
🔷 ElevatedButton

import 'package:flutter/material.dart';

void main(){
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: ElevatedButton(
            // 클릭시 실행
            onPressed: (){
              print('버튼 눌림');
            },

            // 스타일 지정하기
            style: ElevatedButton.styleFrom(
              // 주 색상 지정
              backgroundColor: Colors.red,

            ),
            child: Text('엘리베이티드 버튼', style: TextStyle(color: Colors.white,),),
          ),
        ),
      ),
    );
  }
}

 

 

반응형

'Flutter' 카테고리의 다른 글

다트 이해하기 - (2) 내장 변수타입  (0) 2024.12.01
다트 이해하기 - (1) 변수  (0) 2024.12.01
typedef와 함수  (0) 2024.03.30
dart 이론  (1) 2024.03.30
TodoList App 만들기  (0) 2024.03.21
반응형

typedef 키워드는 함수의 시그니처를 정의하는 값으로 보면 됩니다.

여기서 시그니처란 반환값 타입, 매개변수 개수와 타입 등을 말합니다. 즉 함수 선언부를 정의하는 키워드입니다.

함수가 무슨 동작을 하는지에 대한 정의는 없습니다.

 

typedef Operation = void Function(int x, int y);

void add(int x, int y) {
  print('결과값 : ${x + y}');
}

void subtract(int x, int y) {
  print('결과값 : ${x - y}');
}


void main () {
  // typedef는 일반적인 변수의 타입처럼 사용가능
  Operation oper = add;
  oper(1, 2);

  // subract() 함수도 Operation에 해당되는 시그니처이므로 oper 변수에 저장 가능
  oper = subtract;
  oper(1, 2);
}
결과값 : 3
결과값 : -1
반응형

'Flutter' 카테고리의 다른 글

다트 이해하기 - (1) 변수  (0) 2024.12.01
플러터 기본위젯  (0) 2024.03.30
dart 이론  (1) 2024.03.30
TodoList App 만들기  (0) 2024.03.21
Flutter MVVM 패턴 - 1  (0) 2024.03.19
반응형

📘 var를 사용한 선언

변수는 var 변수명 = 값; 으로 선언합니다. 변수에 값이 들어가면 자동으로 타입을 추론하는 타입 추론 기능을 제공하므로 명시적으로 타입을 선언하지 않아도 됩니다.

void main (){
	
    var name = '주녘주녘';
    print(name);
    
    // 변수의 값 변경가능
    name = '주녘';
    print(name);
    
    // 변수명 중복은 불가
    // var name = '플러터의신'; <- 오류남

}

 

📘 dynamic을 사용한 변수선언

var 타입은 변수의 값을 사용해서 변수의 타입을 유추하는 키워드입니다. 타입을 한 번 유추하면 추론된 타입이 고정됩니다.

따라서 고정된 변수 타입과 다른 변수 타입의 값을 같은 변수에 다시 저장하면 에러가 발생합니다. 하지만 dynamic 키워드를 사용하면 변수의 타입이 고정되지 않아서 다른 타입의 값을 저장할 수 있습니다.

 

void main() {

	dynamic name = '주녘주녘';
	name = 1;

}

 

📘 final/const를 사용한 변수 선언

final과 const는 변수의 값을 처음 선언 후 변경할 수 없습니다.

void main () {

	final String name = '뉴진스';
 	name = '르세라핌';	// 에러발생 final로 선언한 변수는 선언 후 값을 변경 불가
    
    
  	const String name2 = '아이브';
 	name2 = '뉴진스';	// 에러발생 final로 선언한 변수는 선언 후 값을 변경 불가

}

 

- final은 런타임, const는 빌드타임 상수입니다.

- 코드를 실행하지 않은 상태에서 값이 확정되면 const를, 실행할 때 확정되면 final을 사용!

 

📘 컬렉션

여러 값을 하나의 변수에 저장할 수 있는 타입입니다.

여러 값을 순서대로 저장하거나(List), 특정 키값 기반으로 빠르게 값을 검색해야 하거나(Map), 중복된 데이터를 제거할 때(Set) 사용됩니다. 컬렉션 타입은 서로의 타입으로 자유롭게 형변환이 가능하다는 큰 장점이 있습니다.

 

🔷 List 타입

- 여러 값을 순서대로 한 변수에 저장할 때 사용

- 리스트의 구성 단위를 원소라고 합니다.

- 리스트명[인덱스] 형식으로 특정 원소에 접근 가능합니다.

- 인덱스는 원소의 순번

- 제일 첫 원소는 0으로 지정, 마지막 원소는 리스트 길이 -1 로 지정

void main() {

  List<String> iveList = ['유진', '원영', '이서', '레이', '리즈', '가을'];

  print('----------- 전체 리스트를 출력해보자 -----------');
  print(iveList);
  print('----------- 첫번째 원소를 출력해보자 -----------');
  print(iveList[0]);
  print('----------- 다섯번째 원소를 출력해보자 -----------');
  print(iveList[4]);
  print('----------- 리스트 길이를 출력해보자 -----------');
  print(iveList.length);

  print('----------- 인덱스 값 변경 -----------');
  iveList[3] = '주녘';

  print('----------- 전체 리스트를 출력해보자 -----------');
  print(iveList);
}
출력값



----------- 전체 리스트를 출력해보자 -----------
[유진, 원영, 이서, 레이, 리즈, 가을]
----------- 첫번째 원소를 출력해보자 -----------
유진
----------- 다섯번째 원소를 출력해보자 -----------
리즈
----------- 리스트 길이를 출력해보자 -----------
6
----------- 인덱스 값 변경 -----------
----------- 전체 리스트를 출력해보자 -----------
[유진, 원영, 이서, 주녘, 리즈, 가을]

 

- 리스트 길이는 length를 가져와 확인할 수 있습니다.

 

📘 add() 함수

add() 함수는 List에 값을 추가할 때 사용되며 추가하고 싶은 값을 매개변수에 입력하면 됩니다.

void main() {

  List<String> iveList = ['유진', '원영', '이서', '레이', '리즈', '가을'];

  iveList.add('주녘'); // 리스트의 끝에 추가됨

  print(iveList);
}
/Users/junhyuk/flutter/bin/cache/dart-sdk/bin/dart --enable-asserts /Users/junhyuk/Desktop/untitled/lib/main.dart
[유진, 원영, 이서, 레이, 리즈, 가을, 주녘]

Process finished with exit code 0

 

📘 where() 함수

where() 함수는 List에 있는 값들을 순서대로 순회하면서 특정 조건에 맞는 값만 필터링하는데 사용됩니다.

매개변수에 함수를 입력해야하며, 입력된 함수는 기존 값을 하나씩 매개변수로 입력받습니다. 각 값별로 true를 반환하면 값을 유지하고, false를 반환하면 값을 버립니다. 순회가 끝나면 유지된 값들을 기반으로 이터러블이 반환됩니다.

 

* 이터러블?

- List나 Set 등의 컬렉션 타입들이 상속받는 클래스

- List나 Set 같은 컬렉션이 공통으로 사용하는 기능을 정의해둔 클래스

- where() 이나 map() 등 순서가 있는 값을 반환할 때 사용

 

void main() {

  List<String> iveList = ['유진', '원영', '이서', '레이', '리즈', '가을'];

  final newList = iveList.where((name) => name == '유진' || name == '원영', );  // '유진' 또는 '원영' 만 유지

  print(newList);
  print(newList.toList()); // 이터러블을 리스트로 다시 반환할 때 toList 사용
}
/Users/junhyuk/flutter/bin/cache/dart-sdk/bin/dart --enable-asserts /Users/junhyuk/Desktop/untitled/lib/main.dart
(유진, 원영)
[유진, 원영]

Process finished with exit code 0

 

📘 map() 함수

map 함수는 List에 있는 값들을 순서대로 순회하면서 값을 변경할 수 있습니다.

매개변수에 함수를 입력해야 하며 입력된 함수는 기존 값을 하나씩 매개변수로 받습니다. 반환하는 값이 현재값을 대체하며 순회가 끝나면 이터러블이 반환됩니다.

void main() {

  List<String> iveList = ['유진', '원영', '이서', '레이', '리즈', '가을'];

  final newIveList = iveList.map((name) => '아이브 $name');  // 리스트의 모든 값 앞에 '아이브' 추가

  print(newIveList);

  print(newIveList.toList());
}
(아이브 유진, 아이브 원영, 아이브 이서, 아이브 레이, 아이브 리즈, 아이브 가을)
[아이브 유진, 아이브 원영, 아이브 이서, 아이브 레이, 아이브 리즈, 아이브 가을]

 

반응형

'Flutter' 카테고리의 다른 글

플러터 기본위젯  (0) 2024.03.30
typedef와 함수  (0) 2024.03.30
TodoList App 만들기  (0) 2024.03.21
Flutter MVVM 패턴 - 1  (0) 2024.03.19
dart 비동기 프로그래밍 - 3  (0) 2024.03.15
반응형

🔹 프로젝트 기간 : 2024.03.11 ~ 2024.03.26 (총 12일)

🔹 Git : https://github.com/CyberUniversityProject/CyberUnivProject

 

GitHub - CyberUniversityProject/CyberUnivProject

Contribute to CyberUniversityProject/CyberUnivProject development by creating an account on GitHub.

github.com

🔹 PDF : https://drive.google.com/file/d/1htnvcKOFypTGXwcoz49CLErcanUjPlBU/view?usp=sharing

 

학사관리시스템 프로젝트(CU)_ 보고서

 

drive.google.com

 

 

 

🧑🏻‍💻 프로젝트 선정배경

  • 다양한 프로젝트를 거치면서 ERP 시스템에 대해 팀원 모두가 관심을 가지게 됨
  • 대학생 시절에 사용해본 학사관리시스템을 주제로 선정하여 개발 프로젝트 진행

🧑🏻‍💻 역할

  • 프로젝트 팀장 역할
  • 사이트 기본 디자인 구현
  • 로그인/로그아웃/ID/PW 찾기
  • 교직원 기능
    1. 학생/교수 명단조회
    2. 학생/교수/교직원 등록
    3. 등록금 고지서 발송
    4. 휴학 처리
    5. 수강신청기간 설정
    6. 강의 등록
    7. 단대별 등록금 설정
  • 학생 기능
    1. 수강신청
  • 커뮤니티
  • 캠퍼스맵

 

 

🛠️ 기술 스택

🔻 Front-End

  • HTML5
  • CSS3
  • JavaScript (1.16.1)
  • JQuery (3.6.4)
  • bootstrap (4.6.2)

🔻 Back-End

  • JAVA 17
  • SpringBoot (3.1.8)
  • MyBatis
  • JSP
  • MySQL (8.0.26)
  • lombok
  • Apache Tomcat(10.1.19)

🔻 API

  • 포트원 결제 API
  • 나이스 식단 API
  • 카카오맵 API

🔻 Collaboration

  • Git
  • GitHub Desktop
  • Discord
  • Notion
  • Slack

 

📖 전체 기능 둘러보기

https://youtu.be/G59X8qBfSrg?si=2Mh7UqbNjgkcKI10

 

 

 

📖 주요 담당 기능

🔹 아이디/비밀번호 찾기

  • 이름과 이메일 그리고 계정 유형을 선택하여 해당하는 유저가 있는 경우 아이디 찾기 결과를 보여줌
  • 비밀번호 찾기는 이름과 (아이디 학번(사번)) 이메일을 입력하여 임시비밀번호를 발급하는 형태
  • 일치하는 회원정보가 있다면 임시비밀번호 발급, 임시발급된 비밀번호를 입력하여 로그인

🔹 학생, 교수 명단조회 + 검색기능

https://youtu.be/BP67nSln-aM?si=QIcM-G5nhwsaGnXs

  • 등록된 학생, 교수들의 목록을 확인할 수 있음
  • 학과 번호 또는 학번 검색 기능

🔹 학생, 교수, 교직원 등록

https://youtu.be/IpTRca01Cik?si=ivBCTcWhuPlCB4w4

  • 유저 별 계정 생성 시 직책에 따라 DB에 생성됨
  • 학생의 경우 사진 이미지 업로드 가능

🔹 등록금 고지서 전송

https://youtu.be/gXovzd8V_iM?si=73zckNHgbrlzuV71

  • 교직원이 등록금 고지서 전송 버튼을 누르면 이번 학기까지 재학중인 학생들에게 등록금 고지서 발송
  • 고지서가 생성되었다는 알림창이 뜨게 되며 학생 로그인 > 등록금 납부 조회페이지에서 확인 가능

🔹 휴학처리

https://youtu.be/tcoHlq5n7w4?si=WrRWcdRZMKSM3WkH

  • 학생이 휴학신청에서 휴학원 양식을 작성 후 신청을 하면 교직원이 신청 내역을 확인할 수 있음
  • 교직원이 신청 내역을 확인 후 휴학 승인 또는 반려처리

🔷 수강신청기간 설정

https://youtu.be/exCFo_XNTDU?si=QKVM3C98vyE5K6JJ

  • 기본값은 수강신청기간 종료
  • 예비수강신청 > 수강신청 > 종료 순으로 변경

https://youtu.be/pU1mA1hMil8

[예비 수강신청]

  • 대상 : 현재 학기에 재학 상태가 되는 학생
  • 신청/취소할 때마다 강의 현재 인원 변경
  • 신청 강의의 정원 초과 가능 (찜하기 기능과 유사)
  • 최대 수강 가능 학점 초과 불가능 (최대 18학점)
  • 신청자 본인의 시간표와 겹치는 강의 신청 불가능

[수강신청]

  • 정원 ≥ 신청인원인 강의 : 예비 수강 신청 내역이 수강 신청 내역으로 자동으로 이월됨
  • 정원 < 신청인원인 강의 : 신청 인원이 0명으로 초기화되며, 학생이 직접 신청해야 함
  • 예비 수강 신청 내역이 있는 경우, 신청이 용이하도록 수강 신청 탭에 가장 먼저 출력됨
  • 수강신청 대상 : 현재 학기에 재학 상태가 되는 학생
  • 신청/취소할 때마다 강의 현재 인원 변경
  • 신청 강의의 정원 초과 불가능
  • 최대 수강 가능 학점 초과 불가능 (최대 18학점)
  • 신청자 본인의 시간표와 겹치는 강의 신청 불가능
  • 페이징 처리, 검색 기능
반응형
반응형
🚩TodoList App 만들기 목표
1. 폴더 구조 잡기
2. main 파일 만들어 보기
3. Model 클래스 생성해보기
4. View 만들어 보기
5. ViewModel 만들어 보기
6. view(todo_list_view.dart) 에 데이터 분리 하기

 

 

1. 프로젝트 구조 구성하기

2. main.dart 코드 작성하기

import 'package:flutter/material.dart';

void main() {
  runApp(const TodoApp());
}

class TodoApp extends StatelessWidget {
  const TodoApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: SafeArea(
        child: Scaffold(
          appBar: AppBar(title: const Text("TodoList"),),
          body: Center(
            child: Text("My Todo"),
          ),
        ),
      ),
    );
  }
}

 

import 'package:flutter/material.dart';
import 'package:my_todo_mvvm/views/todo_list_view.dart';

void main() {
  runApp(const TodoApp());
}

class TodoApp extends StatelessWidget {
  const TodoApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: SafeArea(
        child: Scaffold(
          appBar: AppBar(title: const Text("TodoList"),),
          body: TodoListView(),
        ),
      ),
    );
  }
}

 

3. Model 클래스 생성해보기

/models/todo_item.dart 파일 생성하기

// Model
class TodoItem {
  String title;
  bool isDone;

  TodoItem({required this.title, this.isDone = false});
}

 

 

4. view 만들어 보기

/views/todo_list_view.dart 파일 생성하기 (1단계)

import 'package:flutter/material.dart';


// View 클래스 

class TodoListView extends StatefulWidget {
  const TodoListView({super.key});

  @override
  State<TodoListView> createState() => _TodoListViewState();
} // end of TodoListView class


class _TodoListViewState extends State<TodoListView> {

  final TextEditingController _controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Column(
        children: [
          TextField(
            controller: _controller,
            decoration: InputDecoration(
              hintText: 'Enter todo item...',
              suffix: IconButton(
                icon: Icon(Icons.add),
                onPressed: () {
                 setState(() {
                   // build() 메서드 재 호출
                   _controller.clear();
                 });

                },
              )
            ),
          ),


        ],
      ),
    );
  }
} // end of _TodoListViewState

 

TextEditingController는 TextField 위젯에서 사용자 입력을 관리하는데 사용되는 클래스입니다. 이를 통해 TextField에서 사용자가 입력한 값에 접근하거나, TextField의 값을 변경하거나, TextField를 초기화하는 등 다양한 작업을 수행할 수 있습니다.

  1. 입력값 접근:
    • *TextEditingController*는 _controller.text 프로퍼티를 통해 현재 **TextField*에 입력된 값을 가져올 수 있습니다. 사용자가 **TextField*에 입력한 내용을 가져와서 로직에 활용하거나 다른 곳에 표시할 수 있습니다.
  2. 입력값 변경:
    • *_controller.text = 'newValue'*와 같이 **TextEditingController*의 text 프로퍼티를 통해 **TextField*의 값을 프로그래밍 방식으로 변경할 수 있습니다.
  3. 입력값 초기화:
    • *TextEditingController*의 clear 메서드를 사용하여 **TextField*의 내용을 쉽게 지울 수 있습니다. 이 기능은 사용자가 입력을 완료한 후 입력 필드를 초기화할 때 유용합니다.
  4. 입력 변경 감지:
    • *TextEditingController*에 리스너를 추가하여 **TextField*의 값이 변경될 때마다 알림을 받을 수 있습니다. 이를 통해 입력값이 변경될 때 특정 동작을 수행하도록 설정할 수 있습니다.

 

화면 만들기 2단계

import 'package:flutter/material.dart';
import 'package:my_todo_mvvm/models/todo_item.dart';

// View 클래스

class TodoListView extends StatefulWidget {
  const TodoListView({super.key});

  @override
  State<TodoListView> createState() => _TodoListViewState();
} // end of TodoListView class

class _TodoListViewState extends State<TodoListView> {
  final TextEditingController _controller = TextEditingController();

  // 샘플 데이터 만들어 보기
  List<TodoItem> _todoItems = [
    TodoItem(title: '플러터 공부하기', isDone: false),
    TodoItem(title: '낮잠 자기', isDone: true),
  ];

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Column(
        children: [
          TextField(
            controller: _controller,
            decoration: InputDecoration(
              hintText: 'Enter todo item...',
              suffix: IconButton(
                icon: Icon(Icons.add),
                onPressed: () {
                  setState(() {
                    // build() 메서드 재 호출
                    _controller.clear();
                  });
                },
              ),
            ),
          ),
          //
          Expanded(
            child: ListView.builder(
              itemCount: _todoItems.length,
              itemBuilder: (context, index) {
                var item = _todoItems[index];
                // 두개의 인수 값을 받아서 위젯을 리턴 시키면 된다.
                return ListTile(
                  title: Text(item.title),
                  trailing: Checkbox(
                    value: item.isDone,
                    onChanged: (value) {
                      print("value : ${value}");
                      setState(() {
                        _todoItems[index].isDone = value ?? false;
                      });
                    },
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
} // end of _TodoListViewState

 

ViewModel 만들기

 

// ViewModel
import 'package:my_todo_mvvm/models/todo_item.dart';

class TodoListViewModel {

  // 화면 사용될 데이터
  List<TodoItem> _items = []; // private

  // get 메서드 만들어 주기
  List<TodoItem> get items => _items;

  // 리스트에 TodoItem 객체를 추가하는 메서드 만들기
  void addItem(String title) {
    _items.add(TodoItem(title: title, isDone: false));
  }

  void toggleItem(TodoItem todo) {
    todo.isDone = !todo.isDone;
  }

}

 

todo_list_view.dart 수정

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:my_todo_mvvm/models/todo_item.dart';
import 'package:my_todo_mvvm/view_models/todo_list_view_model.dart';

// View 클래스

class TodoListView extends StatefulWidget {
  const TodoListView({super.key});

  @override
  State<TodoListView> createState() => _TodoListViewState();
} // end of class

class _TodoListViewState extends State<TodoListView> {

  final TextEditingController _controller = TextEditingController();
  final TodoListViewModel listViewModel = TodoListViewModel();





  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Column(
        children: [
          TextField(
            controller: _controller,
            decoration: InputDecoration(
              hintText: 'Enter Todo Item',
              suffix: IconButton(
                icon: Icon(Icons.add),
                onPressed: (){
                  setState(() {
                    listViewModel.addItem(_controller.text);
                    _controller.clear();
                  });
                },
              )
            ),
          ),
          //
          Expanded(
            child: ListView.builder(
              itemCount: listViewModel.items.length,
              itemBuilder: (context, index){
                var item = listViewModel.items[index];
                // 두개 인수값을 받아서 위젯을 리턴
                return ListTile(
                  title: Text(item.title),
                  trailing: Checkbox(
                    value: item.isDone,
                    onChanged: (value) {
                      print('value : ${value}');
                      setState(() {
                        listViewModel.toggleItem(item);
                      });
                    },
                  ),
                );
            },),
          )

        ],
      ),
    );
  }
} // end of _TodoListViewState

 

 

반응형

'Flutter' 카테고리의 다른 글

typedef와 함수  (0) 2024.03.30
dart 이론  (1) 2024.03.30
Flutter MVVM 패턴 - 1  (0) 2024.03.19
dart 비동기 프로그래밍 - 3  (0) 2024.03.15
dart 비동기 프로그래밍 - 2  (0) 2024.03.15
반응형

1. 앱 아키텍처란?

앱 아키텍처는 애플리케이션의 전반적인 구조와 구성요소, 그리고 이러한 구성요소 간의 관계와 상호작용을 정의하는 청사진 또는 설계 원칙을 의미합니다. 쉽게 말해, 앱을 구축하는 데 필요한 구성요소와 이러한 구성요소들이 어떻게 함께 작동하고 연결되는지를 설명하는 방법론이라고 할 수 있습니다.

MVC, MVP, MVVM, VIPER 등 너무나 많은 아키텍처가 존재 한다. 하지만 원리는 거의 동일 하다.

역할 별로 레이어를 나눈다.
각 레이어는 각자의 역할에만 집중하게 설계하고 자신 밖에 업무에서 가능한 신경을 끈다.
각 레이어를 나누게 되면 수정 및 테스트 유지 보수가 용이하다.

 

 

2. MVC (Model-View-Controller) 패턴을 사용하는 주된 이유는?

MVC (Model-View-Controller) 패턴은 오랫동안 사용되어온 소프트웨어 디자인 패턴 중 하나입니다. 웹 애플리케이션, 데스크톱 애플리케이션, 그리고 최근에는 모바일 애플리케이션에서도 널리 사용됩니다.

  1. 가독성: 각 구성요소(Model, View, Controller)가 독립적이기 때문에 코드의 구조가 명확해져서 가독성이 향상됩니다.
  2. 확장성: MVC 패턴은 기능의 확장이 필요할 때, 해당하는 부분만을 수정하면 되기 때문에 확장성이 좋습니다. 예를 들면, UI를 변경하고자 할 때 View만 수정하면 되고, 데이터 처리 방식을 변경하고자 할 때는 Model만 수정하면 됩니다.
  3. 재사용성: 각 구성요소가 독립적이므로 재사용이 용이합니다. 특히 Model은 다른 시스템이나 프로젝트에서도 재사용할 수 있습니다.
  4. 분리와 집중: 각 구성요소의 역할이 분리되어 있기 때문에 각 역할에 집중할 수 있습니다. 이로 인해 코드의 품질과 유지 보수성이 향상됩니다.

이러한 이유로 많은 소프트웨어 개발 프로젝트에서 MVC 패턴을 선호하고 사용합니다.

 

 

3. MVVM 패턴에 대해 알아 보자

  • Model: 실제 데이터 및 비즈니스 로직을 포함합니다. 데이터베이스 액세스, 웹 서비스 호출 등의 작업을 수행합니다.
  • View: 사용자에게 보여지는 UI 요소. 버튼, 텍스트박스, 라벨 등의 위젯 또는 컴포넌트를 포함합니다. View는 ViewModel에 직접적으로 연결되어 데이터를 가져옵니다.
  • ViewModel: Model과 View 사이의 중개자 역할을 합니다. Model에서 데이터를 가져오고 View에 표시할 준비를 담당합니다. 또한 View에서 이벤트를 수신하고 그에 따라 Model을 업데이트합니다.

 

 

1단계 : 시나리오 코드 1 - MV 패턴으로 코드 만들어 보기

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomeView(),
    );
  }
}

// View
class HomeView extends StatefulWidget {
  const HomeView({super.key});

  @override
  State<HomeView> createState() => _HomeViewState();
}

class _HomeViewState extends State<HomeView> {

  // 핵심 데이터 선정 - 클래스
  // Model 에서 데이터와 관련된 로직이 포함 된다.
  int _counter = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('뷰 모델 없는 코드를 작성해보기'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('간단한 뷰와 모델 예제'),
            Text('숫자 확인(핵심데이터) ${_counter}'),
            ElevatedButton(
              onPressed: () {
                // 사용자가 버튼을 클릭하면 숫자가 올라가는 기능을 만들자
                setState(() {
                  _counter++;
                });
              },
              child: Text('증감'),
            )
          ],
        ),
      ),
    );
  }
}

증감 버튼을 클릭시 숫자가 증가하는 것을 볼 수 있음

 

1단계에서는 MV 개념으로 코딩을 했다면 ViewModel 이라는 개념을 적용해보자

 

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomeView(),
    );
  }
}

// View
class HomeView extends StatefulWidget {
  const HomeView({super.key});

  @override
  State<HomeView> createState() => _HomeViewState();
}

class _HomeViewState extends State<HomeView> {

  // 뷰에서는 뷰 모델만 바라보면 된다.
  final CounterViewModel viewModel = CounterViewModel();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('뷰 모델 없는 코드를 작성해보기'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('간단한 뷰와 모델 예제'),
            Text('숫자 확인(핵심데이터) ${viewModel.counter}'),
            ElevatedButton(
              onPressed: () {
                // 사용자가 버튼을 클릭하면 숫자가 올라가는 기능을 만들자
                setState(() {
                 viewModel.incrementCounter();
                });
              },
              child: Text('증감'),
            )
          ],
        ),
      ),
    );
  }
}


// 1단계에서는 MV 개념으로 코딩을 했다면 ViewModel 이라는 개념을 적용해보자
// viewModel
class CounterViewModel {
  int _counter = 0;
  int get counter => _counter;

  void incrementCounter(){
    _counter++;
  }
}
반응형

'Flutter' 카테고리의 다른 글

dart 이론  (1) 2024.03.30
TodoList App 만들기  (0) 2024.03.21
dart 비동기 프로그래밍 - 3  (0) 2024.03.15
dart 비동기 프로그래밍 - 2  (0) 2024.03.15
dart 비동기 프로그래밍 -1  (0) 2024.03.14
반응형

이번 팀 프로젝트는 도서관 플랫폼으로 주제를 정하고 사이트 이름은 북허브(BookHub)로 정하였습니다.

작업기간은 2024.02.13 ~ 2024.03.05 (총 15일) 소요되었습니다.

 

이번 프로젝트에서는 저는 부팀장역할을 맡아 사이트 전체 프론트 디자인 부분과

발표 PPT 제작 및

관리자페이지, 포인트 결제 기능부분을 맡아 개발을 진행하였습니다.

 

💡 프로젝트 선정 배경

  • 환경을 생각한 도서 서비스 → 책 생산시 낭비되는 종이를 막기 위해 책 공유 기능을 넣어 자원낭비를 최소화
  • 모임을 개설하여 서로의 의견을 주고 받을 수 있는 모임 서비스
  • 포인트 결제 수수료, 배너광고 수익을 통한 사이트 수익창출
  • 도서 검색, 대출/반납, 도서관 내 포인트를 이용한 베이커리, PC 사용 서비스

 

💡 내가 개발한 기능

  • 메인페이지 전체 디자인
  • 포트원 결제 API를 이용한 포인트상품 결제/환불 기능 구현(사이트 수익모델)
  • 관리자페이지 광고배너 등록/수정/삭제
  • 관리자페이지 상품 등록/수정/삭제

 

💡 기능 개발하면서 힘들었던점

  • 결제 환불 부분에서 직접 api를 만들면서 첫번째 환불 내역은 취소가 되는데 두번째...n번째 내역은 취소가 안되는 오류가 발생해 밤 낮으로 고민하고 검색하고 할 수 있는 방법을 찾은 결과 정상적으로 환불요청 구현을 완료하여서 뿌듯하였음

 

🛠️ 기술 스택

🔻 Front-End

  • HTML5
  • CSS3
  • JavaScript : 1.16.1
  • JQuery : 3.6.4
  • bootstrap : 4.6.2

🔻 Back-End

  • SpringBoot 3.1.8
  • JAVA 17
  • MySQL 8.0.26
  • lombok
  • Apache Tomcat : 9.0
  • MyBatis
  • JSP

🔻 API

  • OAuth2.0 (구글, 카카오, 네이버)
  • 포트원 결제 API
  • 카카오 주소 API

🔻 Collaboration

  • Git
  • GitBash
  • Discode
  • Notion

💡 전체 기능 시연 영상

📖 주요 담당 기능 미리보기

🔷 메인페이지

  • 웹 페이지를 개발하면서 가장 공들인 부분
  • 사이트 로고도 직접제작하여 실제로 운영하는 사이트 처럼 디자인함

 

🔷 포트원 API를 이용한 포인트 상품 결제

  • 저번 게임쇼핑몰 프로젝트에는 토스페이먼츠 api를 이용하였는데 확실히 포트원 api가 사용하기가 쉬웠다.

 

🔷 포트원 API를 이용한 포인트 상품 환불

  • 가장 시간을 많이 쓴 작업
  • 환불로직에 대한 이해가 부족해서 몇번이고 공식문서를 정독해서 완성한 기능이자 애착이 가는 기능

 

🔷 관리자 페이지 배너 등록

  • 수익 모델을 생각하다가, 배너 광고 클릭시 클릭 횟수마다 금액을 지정하면 괜찮을거 같아서 만든 기능
  • 한번 클릭시마다 550원이 광고수익으로 넘어간다.

 

 

🔷 관리자 페이지 상품 등록

  • 위에서 봤던 포인트 결제 상품들을 등록하는 관리자 페이지 부분
  • BookHub 포인트 상품을 추가하는 관리자 페이지 부분
  • 관리자 계정으로 로그인 후 포인트 관리 → 포인트 상품 등록 페이지에서 해당상품명, 포인트, 실제 결제될 가격 입력 후 등록
  • 메인 페이지 포인트 구매하기 페이지에 실제 등록된 상품이 보이게 구현

 

✅  총평

  • 1차 프로젝트이다 보니 부족한 부분이 많았고, 구현했다가 시간 부족으로 폐기한 기능들이 많아 아쉬웠다.
  • 팀원들끼리 남아서 서로 피드백하고 회의하면서 작업하는게 작업 퀄리티를 높힐 수 있어서 좋았다.
반응형
반응형
1. JSON 응답 타입을 확인 하자
2. JSON Object Type 의 이해 (동기적 방식 처리)
3. JSON Array Type 의 이해 (동기적 방식 처리)

 

 

1. JSON 응답 타입을 확인 하자 - Resposne → body(String)

// Json Object type -> { "key" : "value"}
// Json Array type -> [{},{},{} ]

 

 

Todo DTO

// TODO : DTO 개념 클래스를 설계할 때 nullable 타입으로 설계하자

class Todo {
  int? userId;
  int? id;
  String? title;
  bool? completed;

  // 기본 생성자
  // 강제성 - 생성자
  Todo(this.userId, this.id, this.title, this.completed);

  // 명명된 생성자2 - Map을 넣으면 Todo 오브젝트가 반환되는 코드를 작성
  // 이니셜 라이져 (변수를 초기화 해주는 문법)
  Todo.fromJson(Map<String, dynamic> json)
      : userId = json['userId'],
        id = json['id'],
        title = json['title'],
        completed = json['completed'];

  @override
  String toString() {
    return '내가 보는 - Todo{userId: $userId, id: $id, title: $title, completed: $completed}';
  }
}

 

import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:dart_future_v1/todo.dart';

void main() async {
  var res = await fetchTodo();

  if (res.statusCode == 200) {
    print("통신 성공");
    Map<String, dynamic> result = json.decode(res.body);
    var todo1 = Todo.fromJson(result);

    print("title : ${todo1.title} ");
    print("completed : ${todo1.completed} ");
  } else {
    print("통신 실패");
  }
}

// 통신을 하는 함수 만들어 보기
Future<http.Response> fetchTodo() async {
  const url = 'https://jsonplaceholder.typicode.com/todos/1';

  final response = await http.get(Uri.parse(url));

  return response;
}

 

실행결과

통신 성공
title : delectus aut autem 
completed : false 

Process finished with exit code 0

 

 

이번에는 user 정보를 파싱해서 출력해보자.

 

 

User.dart

// TODO : DTO 개념 클래스를 설계할 때 nullable 타입으로 설계하자

import 'address.dart';
import 'company.dart';

class User {
  int? id;
  String? name;
  String? username;
  String? email;
  Address? address;
  String? phone;
  String? website;
  Company? company;

  User(this.id, this.name, this.username, this.email, this.address, this.phone,
      this.website, this.company);

  User.fromJson(Map<String, dynamic> json)
      : id = json['id'],
        name = json['name'],
        username = json['username'],
        email = json['email'],
        address = json['address'] != null ? Address.fromJson(json['address']) : null,
        phone = json['phone'],
        website = json['website'],
        company = json['company'] != null ? Company.fromJson(json['company']) : null;

  @override
  String toString() {
    return 'User{id: $id, name: $name, username: $username, email: $email, address: $address, phone: $phone, website: $website, company: $company}';
  }
}

 

 

address.dart

import 'geo.dart';

class Address {
  String? street;
  String? suite;
  String? city;
  String? zipcode;
  Geo? geo;

  Address({this.street, this.suite, this.city, this.zipcode, this.geo});

  Address.fromJson(Map<String, dynamic> json)
      : street = json['street'],
        suite = json['suite'],
        city = json['city'],
        zipcode = json['zipcode'],
        geo = json['geo'] != null ? Geo.fromJson(json['geo']) : null;

  @override
  String toString() {
    return 'Address{street: $street, suite: $suite, city: $city, zipcode: $zipcode, geo: $geo}';
  }
}

 

geo.dart

class Geo {
  String? lat;
  String? lng;

  Geo({this.lat, this.lng});

  Geo.fromJson(Map<String, dynamic> json)
      : lat = json['lat'],
        lng = json['lng'];

  @override
  String toString() {
    return 'Geo{lat: $lat, lng: $lng}';
  }
}

 

 

main.dart

import 'dart:convert';

import 'package:dart_future_v1/user.dart';
import 'package:http/http.dart' as http;

void main() async {


  var res = await fetchUser();

  if(res.statusCode == 200) {
    print("통신 성공");
    Map<String, dynamic> result = json.decode(res.body);
    var user1 = User.fromJson(result);

    print("id : ${user1.id} ");
    print("name : ${user1.name} ");
    print("username : ${user1.username} ");
    print("email : ${user1.email} ");
    print("address : ${user1.address} ");
    print("phone : ${user1.phone} ");
    print("website : ${user1.website} ");
    print("company : ${user1.company} ");


  } else {
    print("통신 실패");
  }





}


// 통신을 하는 함수 만들어 보기
Future<http.Response> fetchUser() async {
  const url = 'https://jsonplaceholder.typicode.com/users/1';

  final response = await http.get(Uri.parse(url));

  return response;
}

 

 

실행결과


C:/devtool/flutter/bin/cache/dart-sdk/bin/dart.exe --enable-asserts C:\devtool\class_flutter\dart_future_v1\lib\main11.dart
통신 성공
id : 1 
name : Leanne Graham 
username : Bret 
email : Sincere@april.biz 
address : Address{street: Kulas Light, suite: Apt. 556, city: Gwenborough, zipcode: 92998-3874, geo: Geo{lat: -37.3159, lng: 81.1496}} 
phone : 1-770-736-8031 x56442 
website : hildegard.org 
company : Company{name: Romaguera-Crona, catchPhrase: Multi-layered client-server neural-net, bs: harness real-time e-markets} 

Process finished with exit code 0
반응형

'Flutter' 카테고리의 다른 글

TodoList App 만들기  (0) 2024.03.21
Flutter MVVM 패턴 - 1  (0) 2024.03.19
dart 비동기 프로그래밍 - 2  (0) 2024.03.15
dart 비동기 프로그래밍 -1  (0) 2024.03.14
블로그 웹 앱 만들어보기  (0) 2023.12.15
반응형

JSON 형식에 문자열 데이터를 dart 코드로 변환 처리

JSON 문자열을 Dart 객체로 변환하는 과정을 "JSON 파싱"이라고 합니다.
JSON 파싱은 일반적으로 JSON 형식의 문자열 데이터를 특정 프로그래밍 언어의 데이터 구조나 객체로 변환하는 작업을 의미합니다.

 

Dart 객체로 변환하기 위해 우리는 dart:convert 라이브러리의 json.decode() 메서드를 사용

 

import 'dart:convert';

void main() {

  // 1단계 - 통신을 x 직접 json 형식의 데이터를 만들어 보자
  String jsonStr = '''
        {
          "userId": 1,
          "id": 100,
          "title": "json 파싱이란?",
          "completed": false   
        }
   ''';
  // 위 코드는 단지 형식이 있는 문자열입니다 (json)

  // 1단계 - 라이브러리가 필요 하다.
  // JSON 문자열을 파싱하여 먼저 Map 객체로 변환을 해야 한다.
  Map<String, dynamic> jsonStrToMap = json.decode(jsonStr); // Map 구조로 변환 해줌
  print(jsonStrToMap.runtimeType);
  print(jsonStrToMap);
  // Map --> 반복문 활용을 많이 한다.
  jsonStrToMap.forEach((key, value) {
    print("key - ${key}" );
    print("key - ${value}" );
    print("---------------------");
  });
}

class Todo {

  int userId;
  int id;
  String title;
  bool completed;

  // 기본 생성자 1
  // 강제성 - 생성자
  Todo(this.userId, this.id, this.title, this.completed);

  // 명명된 생성자 2 - Map를 넣으면 Todo 오브젝트가 반환 되는 코드를 작성
  // 이니셜 라이져 (변수를 초기화 해주는 문법)
  Todo.fromJson(Map<String, dynamic> json)
    : userId = json["userIkd"],
      id = json["id"],
      title = json["title"],
      completed = json["completed"];

  @override
  String toString() {
    return '내가 보는 - Todo{userId: $userId, id: $id, title: $title, completed: $completed}';
  }
}

 

실행결과

C:/devtool/flutter/bin/cache/dart-sdk/bin/dart.exe --enable-asserts C:\devtool\class_flutter\dart_future_v1\lib\main8.dart
_Map<String, dynamic>
{userId: 1, id: 100, title: json 파싱이란?, completed: false}
key - userId
value - 1
-------------------
key - id
value - 100
-------------------
key - title
value - json 파싱이란?
-------------------
key - completed
value - false
-------------------
내가 보는 - Todo{userId: 1, id: 100, title: json 파싱이란?, completed: false}
json 파싱이란?

Process finished with exit code 0
반응형

'Flutter' 카테고리의 다른 글

TodoList App 만들기  (0) 2024.03.21
Flutter MVVM 패턴 - 1  (0) 2024.03.19
dart 비동기 프로그래밍 - 3  (0) 2024.03.15
dart 비동기 프로그래밍 -1  (0) 2024.03.14
블로그 웹 앱 만들어보기  (0) 2023.12.15
반응형
동기성 - 모든 코드가 순차적으로 진행되는 형태
비동기 - 코드가 동시다발적으로 실행, 순착적으로 보장을 할 수 없는 형태

 

다트 언어에서 제공하는 Future, Stream, await, async는 모두 비동기 프로그래밍을 지원하는 기능입니다.
비동기 프로그래밍이란 시간이 오래걸리는 작업을 실행한 후 끝날 때까지 기다리지 않고 다음 작업을 실행하는 것입니다. 비동기 프로그래밍과 반대되는 개념은 동기 프로그래밍으로 어떤 작업을 실행하고 끝날 때까지 기다렸다가 그 다음 작업을 수행하는 것입니다.
dart 비동기 프로그래밍은 프로그램의 흐름을 중단시키지 않고, 무언가의 완료를 기다리는 동안 다른 작업을 수행할 수 있게 해줍니다. Dart에서는 이를 위해 Future와 Stream이라는 두 가지 주요 개념을 제공합니다.

 

동기로 실행되는 예를 들어 비동기 프로그래밍이 왜 필요한지 알아보겠습니다.

다음 코드는 버튼을 클릭하면 onPress() 함수가 호출되고, onPress() 함수에서 sum() 함수를 호출합니다.

여기서 sum() 함수에서 간단한 더하기를 처리했지만, 시간이 오래 걸리는 작업이라 가정하겠습니다.

void main() {

  onPress();

}

void sum() {
  var sum = 0;
  Stopwatch stopwatch = new Stopwatch();
  stopwatch.start();
  for(int i = 0; i < 500000000; i++) {
    sum += i;
  }
  stopwatch.stop();
  print("${stopwatch.elapsed}, result : $sum");
}

void onPress() {
  print('onPress top...');
  sum();
  print('onPress bottom...');
}

 

[실행결과]

onPress top...
0:00:00.161466, result : 124999999750000000
onPress bottom...

 

실행결과를 보면 onPress top이 출력되고 sum() 함수의 결과가 출력된 후에 onPress bottom 이 출력되었습니다.

즉, sum() 함수가 끝나야 onPress() 함수에서 sum() 함수를 호출한 다음 그 다음 줄이 실행됩니다. 결국, onPress() 함수는 sum() 함수의 실행이 끝날 때 까지 대기합니다.

만약 sum() 함수의 작업이 오래 걸린다면 문제가 됩니다. 왜냐하면 sum() 함수의 작업을 마칠때까지 onPress()의 나머지를 수행할 수 없기 때문입니다.

이처럼 시간이 오래걸리는 작업은 다양한데 네트워킹 또는 파일을 읽거나 쓰는 겨웅가 대표적입니다. 이런 작업을 동기로 프로그래밍하면 그 작업이 끝날 때 까지 사용자 이벤트나 화면을 처리할 수 없습니다. 따라서 앱의 성능이 떨어지는 문제가 있습니다.

// 키워드 묶음 // async / await / Future : 1회만 응답을 돌려 받는 경우
// async* / yield / Stream : 지속적으로 응답을 돌려 받아야 하는 경우

 

시나리오 코드 1 - Future 타입 사용해보기

import 'package:flutter/material.dart';

void main() {


// 비동기 프로그래밍
// 키워드 묶음 - async, await : Future (1회성 응답을 돌려 받는 경우)

print("task ...... 1");
// 비동기 함수 만들기
  var data1 = fetchData();
print("task ...... $data1");
print("task ...... 3");


}

// 비동기 함수 - Future
Future<String> fetchData() {
  // 2초 뒤에 데이터를 가져 옴
  return Future.delayed(Duration(seconds: 2), () {
    return "2초 data";
  });
}

 

Performing hot restart...
Syncing files to device sdk gphone x86...
Restarted application in 761ms.
I/flutter (28571): task ...... 1
I/flutter (28571): task ...... Instance of 'Future<String>'
I/flutter (28571): task ...... 3

 

시나리오 코드 2 - await 사용 (마치 동기 프로그래밍 처럼 활용할 수 있다)

규칙

await 키워드를 사용하는 포함하고 있는 함수는 반드시 함수 선언부에 async 키워드를 선언해주어야 한다.
import 'package:flutter/material.dart';

void main() async {


// 비동기 프로그래밍
// 키워드 묶음 - async, await : Future (1회성 응답을 돌려 받는 경우)

print("task ...... 1");
// 비동기 함수 만들기
  var data1 = await fetchData();
print("task ...... $data1");
print("task ...... 3");


}

// 비동기 함수 - Future
Future<String> fetchData() {
  // 2초 뒤에 데이터를 가져 옴
  return Future.delayed(Duration(seconds: 2), () {
    return "연산완료 처리";
  });
}

 

Performing hot restart...
Syncing files to device sdk gphone x86...
Restarted application in 783ms.
I/flutter (28571): task ...... 1
I/flutter (28571): task ...... 연산완료 처리
I/flutter (28571): task ...... 3

 

이해도를 높이기 위한 코드 작성

import 'package:flutter/material.dart';

void main()  {
  // Future 타입과 값을 꺼내는 방법
  // 비동기 함수를 - 동기성으로 변경하면 값이 자동으로 꺼내졌다.
  // Future 타입 선언
  Future<String> name = Future.value("주녘");
  Future<int> number = Future.value(29);
  Future<bool> isTrue = Future.value(true);

  print(name);
  print(number);
  print(isTrue);
  print("-------------------");
}

 

Performing hot restart...
Syncing files to device sdk gphone x86...
Restarted application in 1,177ms.
I/flutter (29095): Instance of 'Future<String>'
I/flutter (29095): Instance of 'Future<int>'
I/flutter (29095): Instance of 'Future<bool>'
I/flutter (29095): -------------------

 

 

async, await 사용하면?

import 'package:flutter/material.dart';

void main() async {
  // Future 타입과 값을 꺼내는 방법
  // 비동기 함수를 - 동기성으로 변경하면 값이 자동으로 꺼내졌다.
  // Future 타입 선언
  Future<String> name = Future.value("주녘");
  Future<int> number = Future.value(29);
  Future<bool> isTrue = Future.value(true);

  print(name);
  print(number);
  print(isTrue);
  print("-------------------");

  print(await name);
  print(await number);
  print(await isTrue);
  print("-------------------");
}

 

Performing hot restart...
Syncing files to device sdk gphone x86...
Restarted application in 807ms.
I/flutter (29095): Instance of 'Future<String>'
I/flutter (29095): Instance of 'Future<int>'
I/flutter (29095): Instance of 'Future<bool>'
I/flutter (29095): -------------------
I/flutter (29095): 주녘
I/flutter (29095): 29
I/flutter (29095): true
I/flutter (29095): -------------------

 

시나리오 코드 4 - Future 타입과 값 꺼내는 방법 - 2 (Future.then() 사용)

import 'package:flutter/material.dart';

void main()  {
  // Future 타입과 값을 꺼내는 방법
  // 비동기 함수를 - 동기성으로 변경하면 값이 자동으로 꺼내졌다.
  // Future 타입 선언
  Future<String> name = Future.value("주녘");
  Future<int> number = Future.value(29);
  Future<bool> isTrue = Future.value(true);

  print(name);
  print(number);
  print(isTrue);
  print("-------------------");

  // 1번 방법은 동기성으로 만들기
  // 2번 미래 타입을 소화시키는 2번째 방법 : 콜백 메서드의 활용
  // print(await name);
  name.then((value) => print("미래 타입 값 꺼내기 콜백 :  $value"));
  number.then((e) => print("xxxxx :  $e"));
  isTrue.then((value) { print("ooooo : $value"); });
}

 

Performing hot restart...
Syncing files to device sdk gphone x86...
Restarted application in 904ms.
I/flutter (29303): Instance of 'Future<String>'
I/flutter (29303): Instance of 'Future<int>'
I/flutter (29303): Instance of 'Future<bool>'
I/flutter (29303): -------------------
I/flutter (29303): 미래 타입 값 꺼내기 콜백 :  주녘
I/flutter (29303): xxxxx :  29
I/flutter (29303): ooooo : true

 

🎈 Key Point Future 타입을 소화 시키는 방법
1. 동기적 프로그래밍으로 만들어 준다.
2. 비동기 처리를 한다 - 콜백 메서드로 활용 한다.
3. 하나의 비동기 작업을 처리하기 위해 두 가지 방법을 혼합하여 사용할 수도 있다.

 

응용

import 'package:flutter/material.dart';

void main()  {


  // var result1 = addNumber1(100, 200);
  // print(result1);
  // print(result1.runtimeType);
  // result1.then((value) => print("result1 소화 : $value"));
  print('---------------------');
  var result2 = addNumber2(100, 200);
  result2.then((value) => print("result2 소화 : $value"));
  print('---------------------2222222222');
}

Future<int> addNumber2(int n1, int n2){

  return Future.delayed(Duration(seconds: 3), () => n1 + n2);

}

// 응용
// 함수 설계, 인수 2개 int 값을 받아서 2초 뒤에 연산되는 함수를 설계해보자.
// 동기적 방식으로 처리
Future<int> addNumber1(int n1, int n2) async {
  print("함수 시작 1");
  var result = 0;
  await Future.delayed(Duration(seconds: 2), () {
    result = n1 + n2;
  });
  print("함수 완료 2");
  return result;
}

 

import 'package:flutter/material.dart';

void main() async {


  // var result1 = addNumber1(100, 200);
  // print(result1);
  // print(result1.runtimeType);
  // result1.then((value) => print("result1 소화 : $value"));
  print('---------------------');
  var result2 = await addNumber2(100, 200);
  // result2.then((value) => print("result2 소화 : $value")); 동기방식으로 변경하기
  // 동기방식으로 변경하기
  print(result2);
  print('---------------------2222222222');
}

Future<int> addNumber2(int n1, int n2) {

  return Future.delayed(Duration(seconds: 3), () => n1 + n2);

}

// 응용
// 함수 설계, 인수 2개 int 값을 받아서 2초 뒤에 연산되는 함수를 설계해보자.
// 동기적 방식으로 처리
Future<int> addNumber1(int n1, int n2) async {
  print("함수 시작 1");
  var result = 0;
  await Future.delayed(Duration(seconds: 2), () {
    result = n1 + n2;
  });
  print("함수 완료 2");
  return result;
}
반응형

'Flutter' 카테고리의 다른 글

TodoList App 만들기  (0) 2024.03.21
Flutter MVVM 패턴 - 1  (0) 2024.03.19
dart 비동기 프로그래밍 - 3  (0) 2024.03.15
dart 비동기 프로그래밍 - 2  (0) 2024.03.15
블로그 웹 앱 만들어보기  (0) 2023.12.15

+ Recent posts