Skip to main content

깃헙 푸시 권한 에러

· 9 min read
Junseok Yang
Football Loving Programmer

ChatGPT - GitHub Permission Error 403

깃으로 협업을 하면서 프로젝트를 진행하다 보니 코드충돌하는 것을 잡는 데 대부분의 시간을 사용해 버리게 되는 것 같다. 문제는 이러했다.

조장의 깃 레포지터리에 collaborator로 초대를 받았지만 계속해서 내 코드를 레포지터리에 푸시를 하려고 하면 푸시가 거절되는 에러 메시지가 나왔다.

에러 메시지

remote: Permission to Meezzi/flowerring.git denied to footgrammer.
fatal: unable to access 'https://github.com/Meezzi/flowerring.git/': The requested URL returned error: 403

그래서 전지전능하신 ChatGPT 의 답변으로는

means that GitHub is blocking access because the user footgrammer doesn’t have permission to push (or pull, depending on what you were doing) to the repository Meezzi/flowerring.

→ 내가 초대를 받았음에도 불구하고 푸시할 권한이 없다는 내용이었다.


챗 GPT의 해결책


🔍 원인과 해결 방법

1. GitHub 권한 문제

  • 현재 footgrammer 계정으로 로그인 되어 있는데, 이 계정은 Meezzi/flowerring 저장소에 권한이 없어요.

해결 방법:

  • Meezzi 계정으로 로그인하거나
  • 저장소의 소유자(Meezzi)가 footgrammer 계정에게 Collaborator(협업자) 권한을 줘야 해요.
    • 저장소 > Settings > Collaborators > 초대 → 해결 불가…

2. HTTPS 인증 문제 (토큰 혹은 캐시된 사용자 정보)

  • Git이 잘못된 사용자 정보나 토큰을 사용하고 있을 수 있어요.

해결 방법:

bash
복사편집
git config --global --unset credential.helper

그 다음 다시 push할 때 GitHub 사용자명과 패스워드(또는 Personal Access Token)를 입력해야 해요.

→ 해결 불가


3. SSH를 사용하는 방법

HTTPS 대신 SSH 방식을 쓰면 인증 문제가 적어요.

bash
복사편집
git remote set-url origin git@github.com:Meezzi/flowerring.git

그 전에 SSH 키를 등록해둬야 해요 (GitHub > Settings > SSH and GPG Keys).


이것을 하기 전에 HTTPS로 시도를 했다.

예전에 발급한 Personal Access Token이 있기 때문에 한번 시도를 해 봤다.

3️⃣ 올바른 인증 방식 사용하기

👉 HTTPS로 인증하려면 Personal Access Token(토큰) 필요!

GitHub은 이제 기본 비밀번호 대신 토큰 인증을 사용해요.

  1. GitHub 토큰 생성 페이지로 이동

  2. Generate new token (classic) 클릭

  3. repo 권한 포함한 토큰 생성

  4. clone할 때 아래처럼 사용

    bash
    복사편집
    git clone https://<your_token>@github.com/Meezzi/flowerring.git


SSH로 문제 해결하기

bash
복사편집
git remote set-url origin git@github.com:Meezzi/flowerring.git
git push origin main

1️⃣ 새 SSH 키 생성하기

아래 명령어를 실행하세요.

bash
복사편집
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"

  • "your_email@example.com" 부분을 GitHub에 등록된 이메일로 바꿔주세요.
  • 실행하면 경로를 묻는데, 그냥 Enter를 눌러 기본 경로(~/.ssh/id_rsa)에 저장하세요.
  • 비밀번호 입력하는 부분이 나오면 그냥 Enter를 눌러도 되고, 원하면 설정해도 돼요.
    • 나는 그냥 enter로 넘어가 줌

2️⃣ SSH 키를 GitHub에 등록하기

SSH 키를 생성한 후, GitHub에 등록해야 해요.

SSH 키 복사

아래 명령어를 실행해서 SSH 키를 복사하세요.

bash
복사편집
cat ~/.ssh/id_rsa.pub

그러면 다음과 같은 형태의 키가 나올 거예요.

graphql
복사편집
ssh-rsa AAAAB3...yourSSHkey...== your_email@example.com

이걸 전체 복사(Ctrl+C) 하세요.

GitHub에 SSH 키 추가

  1. GitHub SSH 키 등록 페이지로 이동
  2. "New SSH key" 클릭
  3. Title: 원하는 이름 입력 (예: "My Laptop")
  4. Key: 아까 복사한 SSH 키 붙여넣기
  5. Add SSH key 클릭

3️⃣ SSH 연결 테스트

SSH 키가 제대로 등록되었는지 확인하려면, 아래 명령어를 실행하세요.

bash
복사편집
ssh -T git@github.com

성공 메시지 예시

vbnet
복사편집
Hi Meezzi! You've successfully authenticated, but GitHub does not provide shell access.

이 메시지가 나오면 SSH 설정이 완료된 거예요! 🎉


4️⃣ SSH 방식으로 Push 설정하기

이제 origin을 SSH 방식으로 변경하세요.

bash
복사편집
git remote set-url origin git@github.com:Meezzi/flowerring.git

그리고 다시 push 해보세요!

bash
복사편집
git push origin main


이제 SSH를 사용해서 GitHub에 Push할 수 있을 거예요! 🚀


궁금증

HTTPS vs SSH 비교

HTTPS와 SSH는 Git 저장소와 통신하는 두 가지 주요 프로토콜입니다.

HTTPS의 특징

  • 인증 방식: 사용자 이름과 비밀번호 또는 Personal Access Token을 사용
  • 장점:
    • 설정이 간단하고 초보자가 사용하기 쉬움
    • 방화벽이나 프록시에서도 잘 작동
  • 단점:
    • 매번 인증 정보를 입력해야 할 수 있음
    • 토큰 관리가 필요

SSH의 특징

  • 인증 방식: 공개키/개인키 쌍을 사용한 암호화 방식
  • 장점:
    • 한번 설정하면 추가 인증 불필요
    • 더 안전한 보안
    • 자동화된 작업에 적합
  • 단점:
    • 초기 설정이 비교적 복잡
    • 일부 네트워크 환경에서 제한될 수 있음

일반적으로 개발자들은 더 안전하고 편리한 SSH를 선호하며, 특히 팀 프로젝트나 지속적인 개발 작업에서 SSH가 더 효율적입니다.


공개키/개인키 암호화 방식 상세 설명

SSH는 비대칭 암호화를 사용하는데, 이는 공개키와 개인키라는 한 쌍의 키를 사용합니다.

키 쌍의 구성

  • 공개키 (Public Key): 누구나 볼 수 있는 키로, GitHub 서버에 등록됩니다.
  • 개인키 (Private Key): 사용자만 가지고 있는 비밀 키로, 절대 공유하면 안 됩니다.

작동 방식

  1. 사용자가 GitHub에 접속을 시도할 때, GitHub 서버는 암호화된 메시지를 보냅니다.
  2. 이 메시지는 사용자의 공개키로 암호화되어 있어서, 개인키를 가진 사용자만 해독할 수 있습니다.
  3. 사용자의 컴퓨터는 개인키를 사용해 메시지를 해독하고 적절한 응답을 보냅니다.
  4. 이 과정이 성공하면 인증이 완료되고 저장소에 접근할 수 있습니다.

보안상의 이점

  • 비밀번호와 달리 키 쌍은 추측하거나 무차별 대입 공격으로 해킹하기 거의 불가능합니다.
  • 개인키가 노출되지 않는 한 매우 안전한 인증 방식입니다.
  • 중간자 공격(man-in-the-middle attack)으로부터 보호됩니다.

이상으로 오늘의 에러 헨들링 내용은 끝!

깃헙 생각보다 어려운 놈이었다..

플러터 팀 프로젝트 시작

· 9 min read
Junseok Yang
Football Loving Programmer

📚 오늘의 학습 내용

오늘은 새롭게 팀 프로젝트를 시작했다. 간단한 쇼핑몰을 만드는 것이었는데 복잡한 서비스는 아니지만 5명이 함께 팀 프로젝트를 하면서 깃헙 충돌도 나보고 협업을 체험할 수 있었다. 역시 지금까지는 혼자서만 개발을 해 봤기에 이런 경험이 없어서 간단하더라도 이렇게 업무를 분담하고 깃헙으로 협업을 해 보는 경험이 중요한 것 같다.

✍️ 주요 학습 내용

배운 내용

  • 깃헙으로 협업하는 법

새로 알게 된 개념

  • Commit Type
    • 타입은 태그와 제목으로 구성되고 태그는 영어로 쓰되 첫 문자는 대문자로 쓰기
    • 태그 : 제목 의 형태
      • feat : 새로운 기능 추가
      • fix : 버그 수정
      • docs : 문서 수정
      • style : 코드 포맷팅, 세미콜론 누락, 코드 변경이 없는 경우
      • refactor : 코드 리펙토링
      • test : 테스트 코드, 리펙토링 테스트 코드 추가
      • chore : 빌드 업무 수정, 패키지 매니저 수정 영문으로 표기하는 경우 동사(원형)를 가장 앞에 두고 첫 글자는 대문자로 표기(과거 시제를 사용하지 않기)

Branch 관련 git 명령어

브랜치 생성

git switch -c [생성할 브랜치 이름]

브랜치 변경

git switch [브랜치 이름]

브랜치 목록 보기

git branch

브랜치 삭제

git branch -d [삭제할 브랜치 이름]

깃에 업로드하기

git push origin [push할 브랜치 이름]

origin : 원격 저장소 이름

push : 로컬 저장소의 변경사항을 원격 저장소에 업로드

현재 브랜치를 main 브랜치와 동일한 상태를 적용하기

git rebase main
  • 현재 작업 중인(main이 아닌 브랜치)에서 해당 명령어 입력
  • 만약 feat/test에서 명령어를 입력했다면 feat/test/의 최신 커밋을 main과 동일하게 적용됨

Git 명령 되돌리기

git reset --soft HEAD~1
  • reset : 커밋 되돌리기
  • soft : 커밋만 취소, 스테이지는 건들이지 않음
  • HEAD~1 : 가장 최근에 커밋한 1개를 가리킴

브랜치로 작업하는 과정

  1. git pull request extensions 로 브랜치 생성

→ 이 extension을 사용하면 깃헙에서 이슈에 연동해서 브랜치 생성이 가능하고 구현이 끝나면 브랜치를 제거할 수 있음

→ 이슈별로 작업 후 브랜치를 없애면서 작업이 가능

  1. 코드 구현
  2. 커밋 및 푸쉬
  3. Pull Request 제출
  • 개발자가 작업한 브랜치의 코드를 main 브랜치에 병합해 달라고 요청
  1. 코드리뷰
  2. Github 상에서 병합(merge)
  • 누군가가 병합을 했는데 그 코드를 내가 최신화시키지 않고 내가 다시 PR을 하게 되면 내 코드로 최신 코드를 덮어씌울 수 있기 때문에 누군가 merge를 하면 코드를 최신화시키는 것이 중요.
  1. 개발자는 병합된 원격 저장소의 main 브랜치의 코드를 로컬 저장소에 옮겨야 함
  • git pull
  1. 로컬 저장소의 main 브랜치와 원격 저장소의 main 브랜치가 동일하다면 본래 작업하던 브랜치로 돌아와 메인 브랜치와 상태를 동일하게 맞추는 작업이 필요
git rebase main
  1. 다시 코드 구현

git upstream

다른 개발자의 원격 저장소에 있는 변경사항을 가져오는 기능. 이를 통해 협업하고 있는 프로젝트의 최신 업데이트를 받아올 수 있음

  1. Upstream 설정 방법
    1. 로컬 저장소의 터미널에서 git remote add upstream [원격 저장소 URL] 명령어 실행
    2. 이제 원격 저장소를 추가했으며 변경사항을 가져오기 위해 git fetch upstream 명령어 실행가능
  2. upstream 설정 후 사용 방법
    1. git fetch upstream 명령어를 실행하여 원격 저장소의 변경사항을 로컬 저장소로 가져옴
    2. 가져온 변경사항을 로컬 브랜치에 병합하기 위해 git merge upstream/[브랜치 이름] 명령어 실행

명령어

💋 명령어

  1. git remote add upstream [원격 저장소 URL]: 로컬 저장소에 upstream 원격 저장소를 추가합니다.
  2. git remote -v: 현재 설정된 원격 저장소 목록을 확인합니다.
  3. git fetch upstream: upstream 원격 저장소의 변경사항을 로컬 저장소로 가져옵니다.
  4. git merge upstream/[브랜치 이름]: 가져온 upstream 변경사항을 로컬 브랜치에 병합합니다.
  5. git remote remove upstream: upstream 원격 저장소 설정을 제거합니다.

클린코드

  • 섹션을 추출할 때 메소드 가 아닌 StatelessWidget 으로 추출하는 이유는 BuildContext 를 분리해 주기 위함.

  • BuildContext를 분리해 주지 않으면 위젯 트리 상에서 다른 위젯과 공유되는 context를 사용하게 되고 이로 인해 다른 위젯에서 상태 변화로 인해 새롭게 빌드 될 때 상태와 관련이 없는 위젯들도 함께 빌드되는 문제 가 발생할 수 있음

  • 위젯의 리빌드 최소화 를 하기 위해 StatelessWidget을 사용하는 것이 좋음

  • 특정 페이지에서만 사용되는 위젯들은 앞에 _를 추가해 private 접근 제한자를 이용하면 좋음

part of '../product_detail_screen.dart';

class _Header extends StatelessWidget {}
  • part part of 를 활용해서 page위젯과 분리한 섹션 위젯들을 연결해 줄 수 있음
  • 섹션들을 나눠서 관리해 주면 좋음

실습한 내용

  • 우리 조는 먼저 조장의 레포지터리에서 fork를 해 나의 깃헙 레퍼지토리로 가져온 다음에 그 토드를 git clone 을 해서 나의 로컬 저장소에 가지고 왔다.
  • 그 후에는 pull request extension을 통해서 이슈별로 브랜치를 생성해서 개발을 진행하고 푸시를 하고 pull request를 진행했다.
  • 하지만 내 코드가 기존의 코드를 덮을 수 있기 때문에 pull request 를 하기 전에 먼저 git pull 을 통해서 가지고 오는 게 필요

📝 코드 스니펫

// 오늘 배운 주요 코드
git upstream main

📚 내일 학습할 내용

  • 상품 등록 페이지 제작

💭 오늘의 회고

잘한 점 👍

  • 깃헙 모르는 것을 물어봄

개선할 점 🔨

  • 모르는 것을 아는 척 하지 말고 물어보자!

배운 점 💡

  • git 협업

✏️ 참고 자료

플러터 기차 예매 서비스 구현하면서 느낀 점

· 6 min read
Junseok Yang
Football Loving Programmer

📚 오늘의 학습 내용

오늘은 개인과제인 기차예매 앱을 마무리할 수 있었다. 볼 때는 간단해 보였지만 막상 해보니까 생각지도 못한 부분에서 오류도 많이 났고 다트 문법이 아직 부족하다는 부분을 알 수 있었다.

강의를 들을 때는 당연한 것처럼 느껴졌지만 required final const 이런 것들을 언제 붙이고 언제 사용해야 하는지 애매한 부분들도 있었고 생성자도 어떤 식으로 하는 게 좋은지 개념이 확실하지 않다는 것을 알게 되

어서 역시 내가 스스로 해 보는 게 중요하다고 많이 느끼게 되었다.

또한 코드의 가독성을 높이기 위해서 코드를 메소드화하거나 클래스로 따로 빼서 사용하는 등의 작업들도 아직은 익숙하지 않아서 반복되는 작업들은 메소드로 빼는 게 중요하다는 것도 알게 되었다.

✍️ 주요 학습 내용

배운 내용

새로 알게된 개념

  1. 함수 vs 함수()의 차이
onPressed : function
onPressed : function() // return 값이 없으므로 에러

void function(){
print('haha');
}

  • function 과 function() 의 차이를 알지 못했는데 ()가 붙으면 return 값을 가져오겠다는 의미가 있어서 return 값으로 치환되어서 컴퓨터가 인식한다는 것을 알게 되었다.
  1. ListView 오류
  • Column 안에 ListView를 사용하게 되면 오류가 남
  • ListView는 부모 위젯의 높이에 높이를 맞추게 되는데 Column의 높이는 무한이기 때문에 ListView의 높이도 부모위젯을 따라서 무한해지게 되기 때문에 오류가 남 → ListView의 높이를 설정해야 함
  • Expanded 로 감싸서 ListView를 사용하는 것을 추천
  1. SafeArea 사용
  • 버튼이 화면 맨 밑에 붙어 있어서 나는 패딩으로 올릴 생각을 했는데 SafeArea로 감싸주면 너무 간편하고 이쁘게 화면구성을 할 수 있었음
  1. Navigator 사용
  • 특정 페이지로 이동
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return SeatPage(
departure: departure!,
arrival: arrival!,
);
},
),
);
  • 전 화면으로 이동
Navigator.pop(context)

// 특정 데이터를 전 화면으로 보낼 수 있음
Navigator.pop(context, result)
  • 데이터를 받을 때
onTap : () async {
String? result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return StationListPage(title, departure, arrival);
},
),
);

}
  1. ??
arrival ?? '선택'
  • arrivalnull 이면 ‘선택’을 arrival에 넣으라는 의미
  1. builder로 감싸기
  • 코드를 메서드화하거나 클래스로 분리하다 보면 때때로 context를 받을 때가 필요한데 그럴 때 나는 인자로 context를 갖고 와서 쓰고 있었는데 그렇게 하지 않고 builder widget으로 감싸면 쉽게 context를 받아서 쓸 수가 있었음..
Expanded stationArea(String title, String station) {
return Expanded(
child: Builder(
builder: (context) {
return GestureDetector(
onTap: () async {
String? result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return StationListPage(title, departure, arrival);
},
),
);
if (result != null) {
setState(() {
if (title == '출발역') {
departure = result;
} else {
arrival = result;
}
});
}
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
title,
style: TextStyle(
fontSize: 16,
color: Colors.grey,
fontWeight: FontWeight.bold,
),
),
Text(station, style: TextStyle(fontSize: 40)),
],
),
);
},
),
);
}
  1. DefaultTextStyle
  • DefaultTextStyle로 감싸서 내부에 있는 child 안에 text style을 한 번에 지정해 줄 수 있음
  1. 테마 컬러
  • 테마에서 설정한 색상들을 사용할 때
  • color : Theme.of(context).[색상명]
  1. List.generate
List.generate(20, (index) => Widget())
-> 20개의 위젯을 담은 List<Widget>이 반환됨
-> ...List.generate(20, (index) => Widget())
-> 이렇게 하면 바로 사용할 수 있음.

실습한 내용

  • 다크 테마 적용
  • Navigator 활용

📚 내일 학습할 내용

  • 팀플 시작

💭 오늘의 회고

잘한 점 👍

  • 코드 리펙토링

개선할 점 🔨

  • 다트 개념 복습

배운 점 💡

  • 정리해 놓은 내용들

✏️ 참고 자료

플러터 개인과제 기차 예매 앱 구상하기!

· 3 min read
Junseok Yang
Football Loving Programmer

📚 오늘의 학습 내용

오늘은 기차 예매 과제를 시작해서 전체적인 구조를 파악하고 어떻게 할지 설계를 했다.

✍️ 주요 학습 내용

1. 페이지 설계

home_page.dart

Padding - 양 옆
Column - 가운데 정렬
Container
- width : double.infinity
- height : 200
Row
Column (출발역, departureStation)
VerticalDivider (가운데 경계선)
Column (도착역, arrivalStation)

const VerticalDivider(
width: 20,
thickness: 1,
indent: 20,
endIndent: 0,
color: Colors.grey,
),

station_list_page.dart

final List<String> stationLists = [...역들]

Scaffold
AppBar : 왼쪽 , title
ListView
onTap(){ return stationName; }

seat_page.dart

Scaffold
appBar : leftArrow, 좌석 선택
Column
Row
Text : departureStation
Icon :
Text : arrivalStation

Row
SizedBox
Text : 선택됨
SizedBox(width : 20)
SizedBox
Text : 선택 안됨

ListView
RowSeat(int rowNum) * 20
Seat * 4
onTap((){return 좌석 번호})

ElevatedButton
onTap(()=> Navigator)

2. 막히는 부분

1. 좌석 위로 버튼이 보이게 하는 것

  • Stack 을 활용해서 bottom : 20 정도로 해서 ListView와 ElevatedButton을 겹쳐서 하면 되지 않을까? 생각하고 있는데 한번 해봐야 알 수 있을 것 같음

2. 좌석 선택 화면에서 역 재선택 구현

  • 역을 재선택했을 때 역 선택 스택이 쌓이기 때문에 뒤로 가기를 누르면 역선택 화면이 나올 것 같아서 뒤로 가기를 했을 때는 역 선택이 아니라 기차 예매 홈페이지로 이동할 수 있도록 개별적으로 설정해야 할 듯함

실습한 내용

  • 기차 예매 앱 구현

🚨 발생한 문제/에러

  • 아직은 없음

📝 코드 스니펫

// 오늘 배운 주요 코드
No pain, No gain

📚 내일 학습할 내용

  • 주말이지롱

💭 오늘의 회고

잘한 점 👍

  • 그래도 어떤 식으로 구현할지 아이디어가 나와서 괜찮을 듯

개선할 점 🔨

  • git branch 전략을 잘 짜서 하기

배운 점 💡

  • 😊

✏️ 참고 자료

플러터 BMI 앱 만들기

· 8 min read
Junseok Yang
Football Loving Programmer

📚 오늘의 학습 내용

BMI앱을 만들어 보면서 테마 설정 부분을 배울 수 있었다. 라이트 테마, 다크 테마를 통해서 전체적인 색상을 변경할 수 있었고 또 시스템 옵션을 선택하는 것을 통해서 휴대폰의 설정에 맞게 테마를 정할 수도 있었다.

그리고 네비게이터를 활용해서 화면 이동에 대해서 배울 수 있었다. 스택 구조로 위의 화면이 쌓이는 구조고 거기서 한 칸씩 뒤로 나오는 구조였다. 새로운 위젯들도 많이 나왔는데 확실히 처음부터 내가 만들어 봐야 내 것으로 만들 수 있을 것 같다.

✍️ 주요 학습 내용

배운 내용

  • 테마 설정
  • 네비게이터

새로 알게된 개념

프로젝트 폴더 관리

  • lib 안에 pages 폴더를 넣고 그 안에 페이지 별로 폴더를 만들고 그 안에 widgets 폴더를 만들고 그 페이지에서 사용되는 위젯들을 넣음
- lib
- pages
- home
- home_page.dart
- widgets // HomePage 내에서 사용할 위젯들
- result
- result_page.dart
- widgets // ResultPage 내에서 사용할 위젯들
- widgets // 앱 내에서 전체적으로 함께 사용되는 위젯들. 이번 프로젝트에서는 쓸일이 없음.
새로운 위젯
  • Slider

    Slider(
    // 값 - 여기서는 테마 확인을 위해 고정값을 넣었지만
    // 사용할 때에는 변수를 넣고 onChanged에서 해당 변수의 값을 바꿔주고
    // setState 호출해서 사용
    value: 1,
    onChanged: (v) {},
    min: 1, // 최소값
    max: 300, // 최대값
    )
  • CircularProgressIndicator

    import 'dart:math';

    import 'package:flutter/material.dart';

    class ResultGauge extends StatelessWidget {
    const ResultGauge(this.result);
    final double result;
    @override
    Widget build(BuildContext context) {
    return Stack(
    alignment: Alignment.center,
    children: [
    // 가로 세로가 같은 SizedBox를 사용할 때
    SizedBox.square(
    dimension: 250,
    child: CircularProgressIndicator(
    value: 1,
    strokeWidth: 2,
    color: Theme.of(context).dividerColor,
    ),
    ),
    SizedBox.square(
    dimension: 250,
    child: CircularProgressIndicator(
    value: min((result / 35), 1), // BMI max 35!
    strokeWidth: 3,
    color: Theme.of(context).highlightColor,
    ),
    ),
    // Stack 의 크기가 자녀위젯의 크기에 따라 달라지므로 Center 사용 불가
    Text(
    result.toStringAsFixed(1),
    style: TextStyle(fontSize: 20),
    ),
    ],
    );
    }
    }

  • Stack (여러 자식 위젯들을 겹칠 때 사용)

    • 겹치는 거라면서요? GenderBox안의 Container는 안겹치는데요?
      • Stack 내에는 Positioned라는 위젯을 사용 가능
        • Positioned 위젯이란?
          • left, top, right, bottom 속성을 이용해 Stack에 위젯을 자유롭게 배치할 때 사용
        • 사용방법
          Stack(
          children: [
          Positioned(
          child: Container(
          width: 150,
          height: 150,
          color: Colors.red,
          ),
          ),
          Positioned(
          top: 5,
          left: 5,
          child: Container(
          width: 50,
          height: 50,
          color: Colors.green,
          ),
          ),
          Positioned(
          right: 5,
          bottom: 5,
          child: Container(
          width: 50,
          height: 50,
          color: Colors.blue,
          ),
          ),
          ],
          )
          image0.png
    • 주의할 점
      • Stack을 감싸고 있는 부모위젯의 크기가 없을때에는 자식위젯의 가장 큰 위젯을 기준으로 크기가 결정
      • 부모위젯의 크기가 있을 경우 부모위젯의 크기를 기준으로 크기를 결정합니다. image1.png
테마 적용

공식문서

Use themes to share colors and font styles

  • MaterialApp 의 theme, darkTheme 속성에 ThemeData 클래스를 정의해서 사용

  • lib/theme.dart 파일 생성 후 분리

  • 예시

    import 'package:flutter/material.dart';

    final lightTheme = ThemeData(
    useMaterial3: true,
    colorScheme: ColorScheme.fromSeed(
    seedColor: Colors.pinkAccent,
    brightness: Brightness.light,
    ),
    highlightColor: Colors.pinkAccent,
    dividerColor: Colors.black38, // black opacity 38%
    sliderTheme: SliderThemeData(
    activeTrackColor: Colors.black38,
    inactiveTrackColor: Colors.black38,
    trackHeight: 1,
    thumbColor: Colors.pinkAccent,
    ),
    elevatedButtonTheme: ElevatedButtonThemeData(
    style: ButtonStyle(
    shape: MaterialStatePropertyAll(RoundedRectangleBorder()),
    backgroundColor: MaterialStatePropertyAll(Colors.pinkAccent),
    foregroundColor: MaterialStatePropertyAll(Colors.white),
    ),
    ),
    outlinedButtonTheme: OutlinedButtonThemeData(
    style: ButtonStyle(
    shape: MaterialStatePropertyAll(RoundedRectangleBorder()),
    side: MaterialStatePropertyAll(BorderSide(color: Colors.black38)),
    foregroundColor: MaterialStatePropertyAll(Colors.black),
    ),
    ),
    );

    final darkTheme = ThemeData(
    useMaterial3: true,
    colorScheme: ColorScheme.fromSeed(
    seedColor: Colors.pinkAccent,
    brightness: Brightness.dark,
    ),
    highlightColor: Colors.pinkAccent,
    dividerColor: Colors.white30, // white opacity 30%
    sliderTheme: SliderThemeData(
    activeTrackColor: Colors.white30, // 현재값 기준 왼쪽 색상
    inactiveTrackColor: Colors.white30, // 현재값 기준 오른쪽 색상
    trackHeight: 1, // 막대기 높이(두께)
    thumbColor: Colors.pinkAccent, // 동그라미 (현재값)
    ),
    elevatedButtonTheme: ElevatedButtonThemeData(
    style: ButtonStyle(
    shape: MaterialStatePropertyAll(RoundedRectangleBorder()),
    backgroundColor: MaterialStatePropertyAll(Colors.pinkAccent), // 배경색
    // 전경색 (Text 등 child 위젯의 색상)
    foregroundColor: MaterialStatePropertyAll(Colors.white),
    ),
    ),
    outlinedButtonTheme: OutlinedButtonThemeData(
    style: ButtonStyle(
    shape: MaterialStatePropertyAll(RoundedRectangleBorder()),
    side: MaterialStatePropertyAll(BorderSide(color: Colors.white30)),
    foregroundColor: MaterialStatePropertyAll(Colors.white),
    ),
    ),
    );
  • ThemeData 정의

    ThemeData(
    useMaterial3: true, // Material3 디자인 사용여부 2와의 차이는 아래 그림 참조
    // ColorScheme 클래스의 fromSeed 생성자를 사용하면
    // seedColor 를 기준으로 각각 요소들의 색상을 정해줍니다.
    colorScheme: ColorScheme.fromSeed(
    seedColor: Colors.purple,
    // dark, light 에 따라 디자인이 배경 색상 등이 다크모드, 라이트모드로 달라집니다
    brightness: Brightness.dark,
    ),
    // 앱 내에서 사용할 TextTheme 를 정할수도 있는데
    // displayLarge, titleLarge 등의 속성별로 정의할 수 있습니다.
    textTheme: TextTheme(
    displayLarge: const TextStyle(
    fontSize: 72,
    fontWeight: FontWeight.bold,
    ),
    // ···
    titleLarge: GoogleFonts.oswald(
    fontSize: 30,
    fontStyle: FontStyle.italic,
    ),
    bodyMedium: GoogleFonts.merriweather(),
    displaySmall: GoogleFonts.pacifico(),
    ),
    ),
  • 예시

    Widget build(BuildContext context) {
    return MaterialApp(
    themeMode : ThemeMode.dark,
    theme: ThemeData(
    useMaterial3: true,
    highlightColor: Colors.pinkAccent,
    colorScheme: ColorScheme.fromSeed(
    seedColor: Colors.pinkAccent,
    brightness: Brightness.light,
    ),
    dividerColor: Colors.black38, // black opacity 38%
    sliderTheme: SliderThemeData(
    activeTrackColor: Colors.black38,
    inactiveTrackColor: Colors.black38,
    trackHeight: 1,
    thumbColor: Colors.pinkAccent,
    ),
    elevatedButtonTheme: ElevatedButtonThemeData(
    style: ButtonStyle(
    // 꼭지점 둥글게
    shape: WidgetStateProperty(RoundedRectangleBorder()),
    backgroundColor: MaterialStatePropertyAll(Colors.pinkAccent), // 배경색
    // 전경색 (Text 등 child 위젯의 색상)
    foregroundColor: MaterialStatePropertyAll(Colors.white),
    ),
    ),
    outlinedButtonTheme: OutlinedButtonThemeData(
    style: ButtonStyle(
    shape: WidgetStateProperty(RoundedRectangleBorder()),
    side: MaterialStatePropertyAll(BorderSide(color: Colors.black38)),
    foregroundColor: MaterialStatePropertyAll(Colors.black),
    )
    ),
    ),
    darkTheme: ThemeData(
    useMaterial3: true,
    colorScheme: ColorScheme.fromSeed(
    seedColor: Colors.pinkAccent,
    brightness: Brightness.dark,
    ),
    ),
    home: HomePage(),
    );
    }
  • 내부 Container의 가장자리 선 색상을 Theme을 통해서 정할 수 없음 → theme에 색상을 넣어주고 그 색상을 Container에서 사용 가능

  1. Theme에 색상 추가(dividerColor)
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.pinkAccent,
brightness: Brightness.light,
),
dividerColor: Colors.black38, // black opacity 38%
),
  1. Container에서 Theme Color 가져오기

Theme.of(context).[색상명]

Container(
width: 80,
height: 80,
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).dividerColor,
),
),
),
개발 꿀팁

→ 넘기는 변수가 3개 이상일 때는 named parameter 로 하면 클래스 생성시 이름이 나옴

→ toStringAsFixed(0) 소수점 몇 번째 자리까지 나오게 할지 정하는 것

실습한 내용

  • BMI 앱 만들기

🚨 발생한 문제/에러

  • 없었음

📝 코드 스니펫

// 오늘 배운 주요 코드
HomePage({
required this.label,
required this. value,
required this.unit}
);

📚 내일 학습할 내용

  • 개인 과제

💭 오늘의 회고

잘한 점 👍

  • 강의 집중함

개선할 점 🔨

  • 중간에 졸아버림

배운 점 💡

  • 테마 설정

✏️ 참고 자료

플러터 영화관 앱 만들기

· 5 min read
Junseok Yang
Football Loving Programmer

📚 오늘의 학습 내용

오늘은 영화관 앱을 만들어 보면서 State 관리에 대해서도 배울 수 있었고 또한 위젯들을 클래스 및 메소드로 만들어서 관리하는 방법에 대해서 배울 수 있었다.

✍️ 주요 학습 내용

배운 내용

  • StatefulWidget과 StatelessWidget의 차이

새로 알게된 개념

  • StatefulWidget을 상속받은 다음에 setState 함수를 통해서 상태를 업데이트해 줌으로써 빌드를 다시 해 변경된 상태를 앱에 보여줄 수 있게 됨

  • State (상태)라는 것은 UI에 영향을 미치는 데이터

  • StatefulWidget 클래스와 State 클래스가 필요

  • state 값을 변경해 주면 setState를 통해서 값이 변경되었으니 다시 빌드해 달라는 요청을 해야 함

  • color : Colors.grey[200] 이렇게 밝기를 조절할 수 있음

  • container 의 특성

    • child가 없으면 기본적으로 크기가 확장
    • child가 있을 경우에는 child 크기에 맞춰서 줄어듬
    • 그렇기 때문에 child가 있을 때는 width : double.infinity 를 통해서 가로 크기를 최대로 키울 수 있음
  • ElevatedButton

    • flutter에서 제공해 주는 버튼 툴 중 하나.
    • 우측 하단에 그림자가 있어 살짝 떠있어 보이는 입체적인 모양의 버튼
    • SizedBox → ElevatedButton으로 구성해 사이즈 구성 가능
    • style : ElevatedButton.styleFrom(backgroundColor : Colors.ember) → 스타일 변경할 때
  • 유저의 제스처에 반응하고 싶다면 GestureDetector() 사용

  • CupertinoDialog

onPressed : (){
showCupertinoDialog(
context : context,
builder : (context) {
return CupertinoAlertDialog(
title : Text('확인'),
content : Text('예약하시겠습니까?'),
actions : [
CupertinoDialogAction(
child : Text('취소'),
isDefaultAction : true,
onPressed(){
Navigator.of(context).pop();
}
),
CupertinoDialogAction(
child : Text('확인'),
isDestructiveAction : true,
onPressed(){}
)
]
)
}
){

}
}
  • Navigator.pop(context)
    • 플러터 내에서 페이지 이동 시 기본적으로 Stack 구조에 차곡차곡 쌓이는 구조
    • 위 코드는 가장 위에 있는 페이지를 하나 제거(pop)해서 이전 페이지로 돌아가게 하는 코드
    • showCupertinoDialog 함수를 사용하면 SeatPage 위에 CupertinoAlertDialog가 쌓임

실습한 내용

  • 영화관 앱 실습

🚨 발생한 문제/에러

  • 문제/에러 1

    1. 문제/에러 정의

    • 클릭하면 가끔 이상한 행과 열이 클릭이 된다.

    2. 시도한 해결 방법

    3. 최종 해결 방법

    4. 새롭게 알게 된 점

    5. 다음에 비슷한 문제를 만난다면?

📝 코드 스니펫

// 오늘 배운 주요 코드
// StatefulWidget 클래스
// 어떤 상태(State 클래스)를 가지는지 정의하는 클래스
// createState 메소드에서 재정의(override)하여 구현
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title})

final String title;

@override
State<MyHomePage> createState() => _MyHomePageState();
}

// state클래스
// 상태를 관리하고 업데이트하는 클래스
// State<MyHomePage> => MyHomePage 라는 StatefulWidget의 state클래스
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;

void _incrementCounter(){
// 상태(변수) 가 변경되면 setState 함수를 통해
// 플러터 프레임워크에 다시 그려달라고 요청
print('_incrementCounter 호출됨 $_counter');
setState((){
_counter++;
});
}

@override
Widget build(BuildContext context){
print('build 호출됨');
return Scaffold(
body : Center(
child : Text(
'$_counter',
style : TextStyle(fontSize : 100)
)
)
)
}
}

📚 내일 학습할 내용

  • 플러터 강의 완강

💭 오늘의 회고

잘한 점 👍

  • 빡공

개선할 점 🔨

  • 혼자서 실습 내용을 처음부터 해볼 필요가 있는 것 같음

배운 점 💡

  • 위젯들

✏️ 참고 자료

플러터 레시피 앱 만들기

· 6 min read
Junseok Yang
Football Loving Programmer

📚 오늘의 학습 내용

오늘에 코딩
class Solution {
bool isPalindrome(String s) {
String result = s.replaceAll(RegExp('[^a-zA-Z]'), "").toLowerCase();

String reverseResult = result.split('').reversed.join();

if (result == reverseResult) {
return true;
} else {
return false;
}
}
}

void main() {
Solution solution = Solution();

print(solution.isPalindrome("A man, a plan, a canal: Panama"));
print(solution.isPalindrome("race a car"));
}

알게 된 점

  • 정규표현식

정규표현식 (Regex) 정리

[xy]

  • 문자 선택을 표현하며 x와 y 중에 하나를 의미한다.

[^xy]

  • ^ 은 not을 표현하며 x 및 y를 제외한 문자를 의미한다.

  • replaceAll([정규표현식], ‘[넣고 싶은 문자열]’)

RegExp(’RegExp('[^a-zA-Z]'))

  • 영어 소문자 및 대문자가 아닌 모든 것을 ‘’로 바꾸는 작업

  • 문자열.toLowerCase() → 소문자로 바꾸기

  • 문자열.toUpperCase() → 대문자로 바꾸기

  • 문자열 거꾸로 바꾸기

    • string.split(’’).reversed.join()
    • split(’’) : 모든 문자를 쪼개서 리스트로 만들어 줌
    • reversed : 배열 순서를 거꾸로 뒤집어 줌
    • join() : 문자열의 리스트를 주어진 문자로 연결한다.
    const t = words.join('.'); //
    print(t); // root.*.0.0.System Administrator./var/root./bin/sh

✍️ 주요 학습 내용

배운 내용

  • AppBar()
  • 폰트 파일 추가
  • 클래스는 대문자로 구분하는 파스칼 표기법 사용
  • 파일이름은 소문자로 작성하고 띄어쓰기는 _로 구분짓는 스네이크 표기법 사용
    • 이것을 코드 컨벤션이라고 함.

새로 알게된 개념

컴포넌트화 하기

목적

  1. 코드의 간결화
    1. Scaffold 내에서 모든 요소들을 구현할 수도 있지만 그렇게 될 경우 한 파일에 코드가 너무 길어짐
    2. 가독성 떨어짐
  2. 코드의 재사용
  3. 유지보수 편의성
폰트 추가
# 각각의 font weight에 해당하는 폰트파일을 지정해두면
# Text 위젯의 style에서 fontWeight를 지정하면 해당 fontWeight에
# 해당하는 폰트를 지정해줌
fonts:
- family: NotoSansKR
fonts:
- asset: fonts/NotoSansKR-Thin.ttf
weight: 100
- asset: fonts/NotoSansKR-ExtraLight.ttf
weight: 200
- asset: fonts/NotoSansKR-Light.ttf
weight: 300
- asset: fonts/NotoSansKR-Regular.ttf
weight: 400 # FontWeight.normal
- asset: fonts/NotoSansKR-Medium.ttf
weight: 500
- asset: fonts/NotoSansKR-SemiBold.ttf
weight: 600
- asset: fonts/NotoSansKR-Bold.ttf
weight: 700 # FontWeight.bold
- asset: fonts/NotoSansKR-ExtraBold.ttf
weight: 800
- asset: fonts/NotoSansKR-Black.ttf
weight: 900

MaterialApp 의 theme 속성에 추가

MaterialApp(
theme : ThemeData(fontFamily : 'NotoSansKR'),
home : const MyHomePage(),
)
AppBar()

image.png

  • leading
  • title
  • actions
  • flexibleSpace
  • bottom
아이콘
Icon(
Icons.favorite,
color : Colors.pink,
size : 24.0
)
  • CupertinoIcons → 아이폰 친화된 아이콘들
Container Widget
  • width 와 height 크기를 가지는 상자위젯
  • 크기를 지정하지 않고 자식 위젯이 없는 경우 크게 확장하려고 하고 자식 위젯이 있는 경우 자식에 크기를 맞추려고 함
  • Container vs SizedBox
    • Container 에는 margin, padding 등의 속성으로 여백을 지정할 수 있음
      • margin : Container 바깥의 여백
      • padding : Container 안의 여백
    • decoration 속성을 이용해 속성을 이용해 배경색, 테두리, 모서리 둥글기 등을 지정할 수 있음
    • transform 속성을 이용해 자식 위젯을 회전시키거나 확대할 수 있음
    • alignment 속성으로 자식위젯의 정렬을 지정할 수 있음
    • 등등의 속성을 지원하지만 다양한 속성을 지원하는 만큼 SizedBox에 비해서 복잡하다
      • 다른말로 무겁고 성능이 좋지않다(SizedBox에 비해)
      • 단순히 크기만 정할경우 SizedBox, 꾸밀땐 Container 사용
기타 위젯들

ListView Widget

  • 가장 일반적으로 사용되는 스크롤 위젯

AspectRatio()

AspectRatio(
aspectRatio : 16/9,
child : Container (color : Colors.green)
)

ClipRRect

ClipRRect(
borderRadius : BorderRadius.circular(10),
child : Image.assets("assets/test.jpg"),
)

실습한 내용

  • 레시피 앱 제작

🚨 발생한 문제/에러

  • 아직은 없었음

📝 코드 스니펫

// 오늘 배운 주요 코드
ClipRRect(
borderRadius : BorderRadius.circular(30),
child : Image.assets("assets/test.jpg"),
)

📚 내일 학습할 내용

  • 플러터 강의 마무리
  • 개인과제

💭 오늘의 회고

잘한 점 👍

  • 마지막 빡집중

개선할 점 🔨

  • 오전 시간 잘 활용하기

배운 점 💡

  • 여러 위젯들

✏️ 참고 자료

플러터 기초 복습

· 4 min read
Junseok Yang
Football Loving Programmer

📚 오늘의 학습 내용

  • 오늘은 환경설정 및 플러터 위젯에 대해서 다시 복습을 했다.
  • 다트를 학습하다 보니까 플러터 위젯에 대해서 많이 까먹은 부분이 있어서 강의를 보면서 다시 복습을 할 수 있었다.

✍️ 주요 학습 내용

배운 내용

  • 플러터 환경설정
  • 플러터 위젯

새로 알게된 개념

  • Column() Widget
  • Row() Widget
  • Text() Widget
  • SafeArea() Widget
  • Spacer() widget
    • 사용 가능한 모든 공간을 차지함
    • flex 옵션을 사용해 크기 비율 조정 가능
  • Padding
    • Options
      • padding : EdgeInsets.all(16.0)
        • all() : 상하좌우
        • symmetric(horizonal : 8.0) : 좌우만
        • symmetric(vertical : 8.0) : 상하만
        • only (left : 40.0)
  • Image
    • Image.network(’url’) : 인터넷의 이미지를 사용
    • Image.asset(’assets/image.png’) : 프로젝트에 추가해준 이미지 사용
    • Image.file(File(’디바이스 내 파일경로’)) : 디바이스 내 저장된 사진
    • Image.memory(bytes) : 메모리 내에 있는 이미지 사용
      • fit 옵션
        • BoxFit.contain : 원본사진의 가로 세로 비율 변화 없음
        • BoxFit.fill : 원본사진의 비율을 무시하고 지정한 영역에 사진을 맞춤
        • BoxFit.cover : 원본사진의 가로 세로 비율을 유지한 채로 지정한 영역에 사진을 맞춤. 장점은 사진의 비율을 유지할 수 있다는 점이고 단점은 사진이 지정한 크기를 벗어나면 잘릴 수 있음
  • Expanded
    • Row, Column 내에서 남은 공간을 확장하여 공간을 채울 수 있도록 하는 위젯
  • SizedBox()
    • width
    • height
    • child

기타

cmd + . → Wrap with widget

flutter devtools

cmd + shift + p(command pallete) : Devtools Widget Inspector을 실행

  • debug 제거
  • MaterialApp의 debugShowCheckedModeBanner : false

사용할 이미지 추가

  • 이미지를 프로젝트 경로 내의 폴더 (/assets)를 만들어 복사해 주기
  • pubspec.yaml에 해당 경로에 assets (이미지 등의 파일)이 있다고 알려주기
assets:
- assets/

main 함수와 runApp()

  • flutter를 시작하기 위해서는 main 함수에서 runApp() 함수를 호출해야 함
  • runApp 호출 시 넘겨주는 위젯이 앱의 루트 위젯이 됨
    • 루트 위젯은 플러터가 그림을 그릴 때 가장 먼저 그리는 위젯

Class 구성

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

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

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

class StorePage extends StatelessWidget {
@override
Widget build(BuildContext context){
return Scaffold();
}
}
  • MyApp 에서는 MaterialApp으로 전체 구조를 잡아주고 StorePage에서는 Scaffold로 구성을 해 줌.

실습한 내용

  • Store 앱

🚨 발생한 문제/에러

  • 없음

📝 코드 스니펫

// 오늘 배운 주요 코드
SizedBox(
height : 10.0,
child : Card(child : Text('hello world')),
}

📚 내일 학습할 내용

  • 플러터 나머지 강의

💭 오늘의 회고

잘한 점 👍

  • 플러터 복습

개선할 점 🔨

  • 아침 시간 활용

배운 점 💡

  • 플러터 위젯들

✏️ 참고 자료

RPG 게임 제작하면서 알게 된 점

· 4 min read
Junseok Yang
Football Loving Programmer

📚 오늘의 학습 내용

  • 오늘 최종적으로 과제를 제출할 수 있었다. 과제를 하면서 확실히 어떻게 코드를 구성해야 하는지 생각해 보고 문법 강의에서 배웠던 내용들을 프로젝트에 사용해 봄으로써 내 것으로 만들 수 있었다.
  • 무지성으로 프로그래밍을 하다 보면 코드가 지저분해지는데 조금 더 코드 리펙토링을 해 가면서 깔끔하게 쓸 수 있도록 바꿔나가는 게 필요할 것 같다.

✍️ 주요 학습 내용

새로 알게된 개념

  • 생성자에서 일부는 미리 지정을 해줄 때가 헷갈렸는데 아래의 두 가지 방법으로 가능했다!

class Game{
late Character character;
List<Monster> monsterList;
int killedMonster = 0;

Game({
required this.character,
required this.monsterList
});

Game(this.character, this.monsterList);

}

입력 받기

  • 입력받을 때 null handling을 이런 식으로 강사님이 하시고 계셨다.
stdout.write('이름을 입력해라 짜슥아');
String? name = stdin.readLineSync();

if(name == null){
return ; // 프로그램 종료
}

몬스터 입력

  • file.readAsLinesSync()
    • 이것을 통해서 줄별로 리스트에 넣어줄 수 있다.
    • 이것을 몰라서 split(’,’) 으로 코딩을 해서 고쳤다.
File file = File('monsters.txt');
List<String> lines = file.readAsLinesSync();
// line 단위로 List에 넣어줌
for(var line in lines){
List<String> stats = line.split(',');
if(stats.length != 3) throw FormatException('Invalid Monster');
String name = stats[0];
String name = int.parse(stats[1]);
String name = int.parse(stats[2]);
}

기타

  • while 조건문에서 bool 변수를 만들어 줘서 코딩을 했는데 isNotEmpty 같은 함수를 활용해서 조금 더 간결하게 코드를 짤 수 있었다.
throw StateError('몬스터가 없습니다');

max(0, attack - monster.defense);
= 큰 값을 골라주는 것

while(monsterList.isNotEmpty){}

정규표현식

RegExp(r'^[a-zA-Z가-힣]+$').hasMatch(name);
// regular expression

결과저장

//game class
saveResult(){}

랜덤 30%

  • 100 중에서 30까지 나오면 실행
Random random = Random();

  • 도전 기능 메서드로 구현하기!
increaseDefence(){
defence += 2;
}

실습한 내용

  • RPG game 만들기

🚨 발생한 문제/에러

  • 문제/에러 1

    1. 문제/에러 정의

    2. 시도한 해결 방법

    3. 최종 해결 방법

    4. 새롭게 알게 된 점

    5. 다음에 비슷한 문제를 만난다면?

📝 코드 스니펫

// 오늘 배운 주요 코드
List<String> lines = file.readAsLinesSync();

📚 내일 학습할 내용

  • 과제 코드 리펙토링

💭 오늘의 회고

잘한 점 👍

  • 과제 마무리한 것

개선할 점 🔨

  • 과제 복습 및 문법 부족한 부분 복습하자

배운 점 💡

  • 여러 가지

✏️ 참고 자료

RPG 게임 완성하기

· 4 min read
Junseok Yang
Football Loving Programmer

📚 오늘의 학습 내용

오늘은 RPG 게임 완성을 하는 데 시간을 많이 쏟았던 것 같다. 코드를 최대한 예쁘게 작성하려고 했지만 만들면 만들수록 점점 지저분해지는 내 코드를 볼 수 있었는데 다시 한번 코드를 보면서 기능별로 함수처리를 해서 예쁘게 만들어야 될 것 같다.

그래도 과제를 하면서 콘솔 입출력에 대해서도 익숙해지게 되고 클래스와 인스턴스의 개념에 대해서도 내가 모르는 부분에 대해서 확실히 알게 되었다. 강의를 들을 때에는 확실히 알고 있다고 생각했지만 역시나 내가 직접 짤려고 하니 required ,final , late 등 언제 무엇을 써야될 지 멘붕이 올 때가 있었다. 그리고 생성자도 다양한 생성방법이 있는데 거기서 내가 어떤 방법을 적용해서 클래스를 만들어야 할지 그런 부분에서도 내가 아직 개념이 부족하다는 것을 알게 되었다.

역시 만들어 보면서 부족한 부분이 많이 나오는 것 같다. 그리고 반복문을 자주 쓰다 보니까 break , continue 의 개념에 대해서도 확실히 알게 된 것 같고 함수, 클래스, 객체 지향 프로그래밍, 값 복사, 참조 복사 이 개념에 대해서 명확히 해서 포스팅을 해야겠다.

역시 내가 개념을 정리해 가면서 공부할 때 내 것이 되는 것 같다.

✍️ 주요 학습 내용

배운 내용

  • 파일출력
  • 정규식

새로 알게된 개념

파일 출력

String contents = '파일 내용';
var file = File('파일 저장 위치');
file.writeAsStringSync(contents);
// 파일 저장 완료!

정규식

RegExp nameRegulation = RegExp(r"^[ㄱ-ㅎ가-힣a-zA-Z]*$");
String text = "검사하고 싶은 문자열";
if(!nameRegulation.hasMatch(text)){
//문자열이 조건에 맞지 않을 때
}
  • 정규식 한글은 r”^[ㄱ-ㅎ가-힣]*$”
  • 정규식 영문은 r”^[a-zA-Z]*$”
  • 정규식 숫자는 r”^[0-9]*$”
  • hatMatch(문자열) 메서드를 통해서 조건일치여부 반환(True or False)

현재시간 얻기

var now = DateTime.now();
int year = now.year;
int month = now.month;
int day = now.day;

실습한 내용

  • RPG Game 만들기

🚨 발생한 문제/에러

없음

📝 코드 스니펫

// 오늘 배운 주요 코드
RegExp nameRegulation = RegExp(r"^[ㄱ-ㅎ가-힣0-9a-zA-Z]*$");
String text = 'Study hard';
if(!nameRegulation.hasMatch(text){
print('Study Again');
}

📚 내일 학습할 내용

  • Code refactoring

💭 오늘의 회고

잘한 점 👍

  • 과제 필수 기능 구현

개선할 점 🔨

  • 도전 기능 구현하기

배운 점 💡

  • 클래스 개념
  • 값 복사, 참조 복사

✏️ 참고 자료