<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>sue0-si.log</title>
        <link>https://velog.io/</link>
        <description>개발 기록</description>
        <lastBuildDate>Sun, 25 Feb 2024 09:26:18 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. sue0-si.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/sue0-si" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Django] Django로 API를 만들어보자 (1) - 프로젝트 세팅, 데이터 api화]]></title>
            <link>https://velog.io/@sue0-si/Django-Django%EB%A1%9C-API%EB%A5%BC-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@sue0-si/Django-Django%EB%A1%9C-API%EB%A5%BC-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Sun, 25 Feb 2024 09:26:18 GMT</pubDate>
            <description><![CDATA[<p>Django REST framework의 <a href="https://www.django-rest-framework.org/tutorial/quickstart/">공식 사이트</a>를 참조하여 만들었다. 하지만 그대로 따라하니 잘 되지 않는 경우도 있어서 따로 정리한다.</p>
<h3 id="1-가상환경-설정">1. 가상환경 설정</h3>
<p>가상환경이 필수는 아닌 것 같지만 나의 경우에는 가상환경을 세팅하지 않은 상태에서는 Django 프로젝트를 생성할 수 없었다...</p>
<p>파이썬이 설치되어 있다는 가정 하에 진행된다.
콘솔에 <code>python3 -m venv env</code> 를 입력하면 현재 폴더에 env라는 이름의 가상환경이 생성될 것이다. 
<img src="https://velog.velcdn.com/images/sue0-si/post/1ce3255a-d53e-4e13-bff5-8c44c11105f7/image.png" alt="">
여기서 주목해야하는 부분은 Scripts 폴더 내에 activate와 deactivate 파일이 있다는 점이다. </p>
<h3 id="2-django-rest-framework-설치">2. Django, REST framework 설치</h3>
<p>가상환경에 Django를 설치한다. env의 상위 폴더로 이동한 후 아래 명령어를 입력하자.</p>
<p><code>env/Scripts/activate</code></p>
<p>경로 앞에 (env)가 붙으면 성공적으로 가상환경이 실행된 것이다. 이 상태에서 아래 명령어로 Django를 설치하자.</p>
<p><code>pip install Django</code></p>
<p>api를 만들기 위해서는 django rest framework를 설치해야 한다. 아래 명령어를 입력하자.</p>
<p><code>pip install djangorestframework</code></p>
<h3 id="3-django-프로젝트-생성">3. django 프로젝트 생성</h3>
<p>프로젝트를 만들고 싶은 위치에서 아래 명령어로 django 프로젝트를 만들자.</p>
<p><code>django-admin startproject 프로젝트_이름</code></p>
<p>생성된 프로젝트로 이동한 후 아래 명령어로 앱을 생성하자. </p>
<p><code>python manage.py startapp 앱이름</code></p>
<p>여기까지 한 후 프로젝트 폴더 안의 settings.py에 가서 INSTALLED_APPS에 아래 코드를 추가하자.</p>
<pre><code class="language-python">INSTALLED_APPS = [
    ...
    &#39;rest_framework&#39;,
    &#39;앱이름&#39;,
]</code></pre>
<h3 id="4-서버-실행">4. 서버 실행</h3>
<p>아래 명령어를 입력하면 서버 실행이 된다.</p>
<p><code>python manage.py runserver</code></p>
<p><img src="https://velog.velcdn.com/images/sue0-si/post/bd2c169e-d050-4d66-bf33-849cfbafda92/image.png" alt=""></p>
<p>빨간글씨는 일단 무시해도 된다. model migration을 안해서 그렇다. 
알려준 주소로 가보면 아래와 같은 창이 뜬다. </p>
<p><img src="https://velog.velcdn.com/images/sue0-si/post/192909ea-7394-4ab0-aaae-d04e6b3594da/image.png" alt=""></p>
<p>🎉성공적으로 django 프로젝트 세팅을 마쳤다🎉</p>
<h3 id="5-model-생성">5. model 생성</h3>
<p>모델은 데이터베이스와 상호 작용하고 데이터의 구조를 정의하는 역할을 한다. </p>
<p>모델은 models.py에 생성한다.</p>
<pre><code class="language-python">from django.db import models

class Users(models.Model):
    user_id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=100)
    created_at = models.DateTimeField(auto_now_add=True)
    loc = models.ForeignKey(Area, on_delete=models.RESTRICT, related_name=&#39;location&#39;, db_column=&#39;loc&#39;)
</code></pre>
<p>필드는 db프로퍼티와 동일하게 설정한다. pk, fk도 고려해야 한다!  </p>
<blockquote>
<ul>
<li>String은 <code>CharField()</code>를 사용한다. 
⚠️ 주의 - <code>max_length</code>를 꼭 정의해줘야 한다.</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li>Int는 <code>IntegerField()</code>, Float는 <code>FloatField()</code>를 사용한다. 디폴트값을 정하고 싶으면 괄호 안에 <code>default=값</code> 을 넣어주면 된다.</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li>primary key: 대부분 자동생성되게 설정하기 때문에 <code>AutoField()</code>를 사용하고, 괄호 안에 <code>primary_key=True</code>를 넣어 pk임을 정의하자.</li>
</ul>
</blockquote>
<blockquote>
<ul>
<li>foreign key: <code>ForeignKey()</code>를 사용해 정의된다. 
  ✨ 필수 인자는 3가지로 참조하는 테이블 이름, 삭제시 제약 (constraints), 역참조시 사용할 이름을 넣어야 한다. 
  ✨ <code>db_column</code>은 이미 만들어놓은 데이터베이스에 모델을 연결할 경우 필수로 넣어야 한다. 필드와 일치하는 db 프로퍼티의 이름을 넣어주면 된다.</li>
</ul>
</blockquote>
<p>데이터베이스 구축을 안 했을 경우 model만 생성하면 sqlite로 데이터베이스가 만들어진다! </p>
<blockquote>
<p>만약 이미 구축된 데이터베이스가 있다면: </p>
</blockquote>
<ol>
<li>settings.py의 DATABASE에서 데이터베이스와 django 프로젝트를 연결해야 한다. 기본은 프로젝트 내의 sqlite3 파일과 연결되어 있으므로 현재 사용중인 DB의 정보를 넣어주자.</li>
<li>class Meta에서 <code>managed=False</code>를 추가하고 <code>db_table</code>에 연결되는 데이터베이스 테이블 이름을 넣자.</li>
</ol>
<p>아래는 이미 구축된 데이터베이스와 연결한 모델 클래스의 완성본이다. 이걸 각 테이블별로 만들면 된다 🤗. </p>
<pre><code class="language-python">from django.db import models

class Users(models.Model):
    user_id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=100)
    created_at = models.DateTimeField(auto_now_add=True)
    loc = models.ForeignKey(Area, on_delete=models.RESTRICT, related_name=&#39;location&#39;, db_column=&#39;loc&#39;)

    class Meta:
            managed = False
            db_table = &#39;admins&#39;</code></pre>
<br>
모델을 다 만들었으면 데이터베이스와 모델을 동기화해야한다. 아래 명령어를 입력하면 데이터베이스에 변화가 생길 것이다.

<pre><code class="language-bash">python manage.py makemigrations snippets
python manage.py migrate snippets</code></pre>
<h3 id="6-serializer-생성">6. Serializer 생성</h3>
<p>serializer는 모델을 json화해준다. restframework 라이브러리에서 제공하기 때문에 import해서 사용하면 된다. serializer에서 model에서 정의하지 않은 필드를 추가해서 json화할 수도 있다.</p>
<p>serializer.py 파일을 생성해서 아래와 같이 코드를 작성해보자.</p>
<pre><code class="language-python"># serializer.py
from rest_framework import serializers
from .models import Users

class UsersSerializer(serializers.ModelSerializer):
    class Meta:
        model = Users
        fields = &#39;__all__&#39;</code></pre>
<p>이런 형식으로 각 모델별 serializer를 만들면 된다. </p>
<h3 id="7-views-생성">7. views 생성</h3>
<p>views는 데이터를 어떻게 화면에 나타낼지를 정의한다. view.py에서 다음과 같이 코드를 작성해보자.</p>
<pre><code class="language-python">from django.http import HttpResponse, JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.parsers import JSONParser
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer

@csrf_exempt
def user(request):
    if request.method == &#39;GET&#39;:
        snippets = Users.objects.all()
        serializer = UsersSerializer(snippets, many=True)
        return JsonResponse(serializer.data, safe=False)

    elif request.method == &#39;POST&#39;:
        data = JSONParser().parse(request)
        serializer = UsersSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data, status=201)
        return JsonResponse(serializer.errors, status=400)</code></pre>
<p>restframework 공식문서의 코드인데, 요청이 GET일 경우 Users 모델의 모든 데이터를 json 형태로 화면에 나타내고, POST일 경우 데이터를 추가하는 코드이다. </p>
<p>url에서 parameter를 받아와서 해당하는 데이터만 화면에 보이게 할 수 있는데, 이건 포스트를 따로 작성할 예정이다! (일단 데이터가 잘 보이게 하는 것에 집중하자)</p>
<h3 id="8-각-테이블에-해당하는-url-생성">8. 각 테이블에 해당하는 url 생성</h3>
<p>프로젝트 구조가 이런 형태일 것이다. 앱 폴더에서 urls.py 파일을 만들자. </p>
<pre><code class="language-bash">├─griffin   // 프로젝트 이름
│  └─__pycache__
└─griffin_flight  // 앱 이름
    ├─migrations
    │  └─__pycache__
    └─__pycache__</code></pre>
<p>프로젝트 폴더 내에 있는 urls.py는 상위 url이고, 앱 폴더 내에 있는 urls.py는 하위 url을 정의하는 곳이다. </p>
<p>프로젝트 폴더 내의 urls.py에서 아래 코드를 넣자.</p>
<pre><code class="language-python">from django.urls import path, include

urlpatterns = [
    path(&#39;/griffin&#39;, include(&#39;앱이름.urls&#39;)),
]</code></pre>
<p>앱이름에 본인의 앱 이름을 넣으면 된다. 나의 경우는 griffin_flight이다. 경로에 아무것도 넣지 않아도 되고, 넣어도 된다. 위 코드의 경우 <code>https://localhost.127.0.0.1:8000/griffin/</code> 형태로 url이 만들어질 것이다.</p>
<br>
앱 폴더 내의 urls.py에서 아래와 같이 코드를 넣자. 

<pre><code class="language-python">from django.urls import path
from . import views

urlpatterns = [
    path(&#39;users/&#39;, views.user),
]</code></pre>
<p><code>https://localhost.127.0.0.1:8000/griffin/</code> 에서 세부 url을 정의하는 것으로, user 데이터에 접근하기 위한 url을 정의했다. 위 코드로 생성된 url은 다음과 같은 형태이다: <code>https://localhost.0.0.1:8000/griffin/users</code> </p>
<h3 id="9-서버-실행-후-url-접속">9. 서버 실행 후 url 접속</h3>
<p>프로젝트 폴더로 이동 후 다음 명령어로 서버를 실행하자.</p>
<p><code>python manage.py runserver</code></p>
<p>만약 로컬서버가 아닌 상황에서 위 명령어로 실행이 안 된다면 <code>python manage.py 0.0.0.0 runserver</code>로 서버를 실행해보자. </p>
<p>이후 정의한 url에 접속하면 데이터가 잘 보일 것이다 🎉🎉🎉</p>
<h3 id="회고">회고</h3>
<p>api를 만들고 싶었는데 많은 자료들이 웹서버 구동을 위한 것이어서 참고자료를 찾기 어려웠다. 시작할때 조금 헤맸어서 과정을 꼭 정리해둬야겠다고 생각했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] 3주간의 프로젝트를 돌아보며]]></title>
            <link>https://velog.io/@sue0-si/Flutter-3%EC%A3%BC%EA%B0%84%EC%9D%98-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%A5%BC-%EB%8F%8C%EC%95%84%EB%B3%B4%EB%A9%B0</link>
            <guid>https://velog.io/@sue0-si/Flutter-3%EC%A3%BC%EA%B0%84%EC%9D%98-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%A5%BC-%EB%8F%8C%EC%95%84%EB%B3%B4%EB%A9%B0</guid>
            <pubDate>Sun, 11 Feb 2024 07:42:25 GMT</pubDate>
            <description><![CDATA[<h2 id="프로젝트-소개">프로젝트 소개</h2>
<p><img src="https://velog.velcdn.com/images/sue0-si/post/8daa96a4-b0d7-4fc3-bc48-361134b0ecef/image.gif" alt=""></p>
<p>3주동안 5명의 팀원들과 함께 플러터로 공용 냉장고 관리 앱을 만들어보는 시간을 가졌다. 이번 프로젝트는 과정과 결과 모두 성공적이었다고 자신있게 말할 수 있다. </p>
<h2 id="프로젝트-과정">프로젝트 과정</h2>
<h3 id="✨1주차-프로젝트-기획-프로토타이핑">✨1주차: 프로젝트 기획, 프로토타이핑</h3>
<p>첫 주는 프로젝트 기획, 프로토타이핑, 팀 그라운드 룰 정하기를 했다. </p>
<h4 id="프로젝트-기획">프로젝트 기획</h4>
<p>회사나 학교 등에서 공용 냉장고를 사용하다 보면 주인 없는 식품들이 제대로 관리되지 않아 폐기되거나 썩게되는 상황이 발생한다. 이를 앱을 통해 음식 보관기한을 정해 이 기한을 넘기면 냉장고 관리자가 폐기해야 하는 음식을 바로 알 수 있는 앱을 만들고자 했다. 하지만 사용자가 앱을 사용하는데에 불편함이 없게 하는 것이 목표였다. </p>
<h4 id="프로토타입">프로토타입</h4>
<p>Figma로 각 화면에 들어갈 필수 기능과 유저 플로우를 구현해보았다. 
<img src="https://velog.velcdn.com/images/sue0-si/post/cc55dd55-1f3c-41fa-9afd-9c451569a468/image.png" alt=""></p>
<h4 id="팀-그라운드-룰">팀 그라운드 룰</h4>
<p>프로젝트에 참여하는 인원이 무려 6명 (거의 현업급!) 이었기 때문에 규칙을 잘 정하는 것이 매우 중요했다. 규칙을 잘 세우고, 팀원 모두 이 규칙을 잘 따라주었기 때문에 팀 화합이 잘 이루어졌다고 생각한다. </p>
<ol>
<li><p>음악을 튼다 🎵
: 스트레스 받지 말고, 긍정적으로 개발을 하기 위해 음악을 튼 상태에서 개발했다.</p>
</li>
<li><p>매일 할일 체크하기 
: 매일 시작 전에 오늘의 Todo를 정하고, 마무리 전 내일의 Todo를 정했다.</p>
</li>
<li><p>매일 같은 시간에 차례로 merge를 하여 모두 동일한 상태의 코드를 유지했다. 이 방법으로 merge conflict도 줄일 수 있었다.  </p>
</li>
<li><p>질문은 부끄러워하지 않기
: 모르는게 있으면 계속 질문하고 토론했다. 모두가 동등한 관계에서 하나의 팀으로서 작업을 할 수 있도록 했다.  </p>
</li>
</ol>
<h3 id="✨2주차-코딩">✨2주차: 코딩</h3>
<p>프로토타입을 기반으로 본격적인 코딩을 시작했다. MVVM 패턴에 freezed, getIt을 사용해 의존성 주입까지 했다. 1명당 페이지 2개를 개발했고, 페이지 공통으로 사용되는 위젯은 widget 폴더에 저장해 코드의 재사용성을 높였다. </p>
<p>프로젝트 구조는 다음과 같다. </p>
<pre><code class="language-bash">├─data
│  ├─models
│  └─repository
├─di
├─domain
├─styles
└─view
    ├─page
    │  ├─change_password_page
    │  ├─discard_foods_page
    │  ├─group_setting_page
    │  ├─login_page
    │  ├─main_my_fridge
    │  ├─my_food_detail_page
    │  ├─my_page
    │  ├─password_reset_page
    │  ├─refrige_detail_page
    │  ├─refrige_pages
    │  ├─register_page
    │  ├─signup_page
    │  └─splash_page
    └─widget
        ├─custom_buttons
        ├─custom_dialog
        ├─custom_widgets
        ├─login_widget
        ├─main_my_fridge_widget
        ├─my_food_detail_page_widget
        └─refrige_detail_page_widget
</code></pre>
<h3 id="✨3주차-푸시-알림-구현-시도--프로젝트-마무리">✨3주차: 푸시 알림 구현 시도 &amp; 프로젝트 마무리</h3>
<p>awesome_notification으로 푸시 알림을 구현하고 코드 리팩토링을 하면서 각종 버그를 수정하는 시간을 가졌다. </p>
<p>원래는 fcm으로 푸시를 구현하고 싶었는데 잘 되지 않았다. firebase console에서는 잘 됬는데 코드상에서는 메시지가 오지 않아 awesome_notification을 사용해봤다. 그 결과 알림이 오긴 하는데 랜덤하게 왔다...😭 어떤 기기에서는 15분만에, 어떤 기기에서는 1시간만에 알림이 왔다. 또한, 앱이 백그라운드 상태이면 대부분의 경우에서는 알림이 오지 않았다. </p>
<p>아래 목록은 그 외에 프로젝트를 하면서 마주친 어려움들과 해결방법이다.</p>
<blockquote>
<ol>
<li>animated_splash_screen으로 구현한 스플래쉬 화면이 UI상으로는 문제없으나 백엔드에서 터지는 문제 -&gt; 패키지에 의존하지 않고 Future.delay로 설정한 시간 뒤에 자동으로 메인페이지로 이동하게 함</li>
<li>비밀번호 변경 페이지에서 reauthenticateWithCredential()을 사용하지 않은 경우 백엔드에서 터지는 문제 -&gt; <a href="https://velog.io/@sue0-si/firebase-updatePassword-%EC%97%90%EB%9F%AC-%EC%B2%98%EB%A6%AC-reauthenticateWithCredential-%EC%A0%81%EC%9A%A9%EB%B0%A9%EB%B2%95">해결 링크</a></li>
<li>firebase를 써본 적이 없어 어디서부터 시작해야 할지 모름 -&gt; <a href="https://firebase.google.com/codelabs/firebase-get-to-know-flutter?hl=ko#0">링크</a>의 codelab이 많은 도움이 되었다!</li>
<li>FutureBuilder로 불러온 이미지의 상태가 다른 위젯의 상태가 변할 때 같이 변하는 문제 -&gt; FutureBuilder를 제거하고 async-await로 이미지 불러옴</li>
</ol>
</blockquote>
<h3 id="마치며">마치며...</h3>
<p>이번 프로젝트를 통해 처음으로 팀으로 일하는 것에 재미를 느꼈다. 이탈자없이 하나의 목표를 향해 모두가 합심하여 유의미한 결과물을 만들어낸 것이 뿌듯했다. 이 앱을 앱스토어에 배포하는 것이 다음 해야할 일이다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[firebase] updatePassword 
에러 처리 (reauthenticateWithCredential() 적용방법)]]></title>
            <link>https://velog.io/@sue0-si/firebase-updatePassword-%EC%97%90%EB%9F%AC-%EC%B2%98%EB%A6%AC-reauthenticateWithCredential-%EC%A0%81%EC%9A%A9%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@sue0-si/firebase-updatePassword-%EC%97%90%EB%9F%AC-%EC%B2%98%EB%A6%AC-reauthenticateWithCredential-%EC%A0%81%EC%9A%A9%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Tue, 06 Feb 2024 16:47:44 GMT</pubDate>
            <description><![CDATA[<h2 id="문제상황">문제상황</h2>
<p>프로젝트에서 비밀번호 변경 페이지를 맡아 개발하고 있었다. firebase를 사용해 이메일/비밀번호로 인증관리를 했는데, firebase에서는 보안상의 이유로 직접 유저의 비밀번호를 가져올 수 없기 때문에 <code>updatePassword()</code>로 비밀번호를 바꿔주었다. </p>
<pre><code class="language-dart">FirebaseAuth.instance.currentUser
                        ?.updatePassword(_newPasswordController.text);</code></pre>
<p>TextField에 입력한 값을 메소드에 전달하면 끝인 줄 알았다. 하지만 변경이 될 때도 있고 안될 때도 있었다. 그리고 콘솔에는 아래와 같은 에러 메세지가 떴다 😭😭😭</p>
<p><img src="https://velog.velcdn.com/images/sue0-si/post/d78f0da0-acc2-401a-8123-916651cd9074/image.png" alt=""></p>
<h2 id="해결">해결</h2>
<p><img src="https://velog.velcdn.com/images/sue0-si/post/de7ce13b-9aa1-4da4-9b65-cd9999ad75d3/image.png" alt=""></p>
<p>updatePassword의 설명은 위와 같다. 읽다보면 중요한 부분이 있는데, 이 함수는 보안의 이유로 재인증을 먼저 해줘야 한다. 즉, reauthenticateWithCredential()을 사용해서 재인증을 한 후 비밀번호를 변경해야 하는 것이다.</p>
<blockquote>
<ol>
<li>현재 credential 저장</li>
</ol>
</blockquote>
<p>현재 비밀번호를 입력하는 TextField를 만들어 유저로부터 현재 비밀번호를 입력받았다. 이 입력값을 credential의 password 필드에 넣어주었다. </p>
<pre><code class="language-dart"> final cred = await EmailAuthProvider.credential(
                          email: currentUser.email!,
                          password: _currentPasswordController.text);</code></pre>
<blockquote>
<ol start="2">
<li>reauthenticateWithCredential 함수 호출</li>
</ol>
</blockquote>
<p>currentUser에 reauthenticateWithCredential()을 호출한다. 인자로는 위에서 저장해둔 credential을 사용한다. </p>
<pre><code class="language-dart">final currentUser = FirebaseAuth.instance.currentUser!;

currentUser.reauthenticateWithCredential(cred);
</code></pre>
<blockquote>
<ol start="3">
<li>then() 안에 updatePassword() 호출</li>
</ol>
</blockquote>
<p>재인증이 끝난 후 비밀번호를 변경하기 위해 then() 안에서 updatePassword()를 호출한다. </p>
<pre><code class="language-dart">currentUser.reauthenticateWithCredential(cred)
    .then((value) async {
           await currentUser
              .updatePassword(_newPasswordController.text);
              // 비밀번호 변경 후 해야하는 작업 추가
              // ex. 로그아웃, 로그인 페이지 이동


    })</code></pre>
<blockquote>
<ol start="4">
<li>reauthenticateWithCredential() 에러 처리</li>
</ol>
</blockquote>
<p><img src="https://velog.velcdn.com/images/sue0-si/post/1fc0e150-90ef-4015-ae70-19a8ecf9f980/image.png" alt=""></p>
<p>reauthenticateWithCredential()에 대한 설명인데, 무려 예외 케이스가 7개나 있다 😭 내 코드에서는 유저가 입력한 현재 비밀번호가 실제 비밀번호와 다른 경우 <strong>invalid-credential</strong> 에러가 발생했다. 그래서 catchError()로 예외 처리를 했다. </p>
<p>아래는 예외처리까지 적용한 코드이다.</p>
<pre><code class="language-dart">
currentUser.reauthenticateWithCredential(cred)
    .then((value) async {
           await currentUser
              .updatePassword(_newPasswordController.text);
    }).catchError((error) {
        if (error is FirebaseAuthException) {
          if (error.code == &#39;invalid-credential&#39;) {
              ScaffoldMessenger.of(context).showSnackBar(
                  const SnackBar(
                     content: Text(&#39;입력하신 현재 비밀번호가 틀렸습니다.&#39;),
                  ),
              );
           } 
        // 다른 예외 경우에 대한 처리 추가하면 좋음
        else {
           ScaffoldMessenger.of(context).showSnackBar(
              const SnackBar(
                  content: Text(&#39;인증 오류가 발생했습니다. 잠시후 다시 시도해주세요.&#39;),
                     ),
              );
            }
     } 
    // FirebaseAuth와 관련되지 않은 예외 처리
    else {
         ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(
                content: Text(&#39;오류가 발생했습니다. 잠시후 다시 시도해주세요.&#39;),
                ),
               );
         }
});</code></pre>
<p>FirebaseAuthException인 경우와 그 외의 예외를 구분했고, FirebaseAuthException 중에서 특정 케이스 (나의 경우 invalid-credential)에 대한 예외 처리를 할 수 있도록 구현해봤다. </p>
<h2 id="소감">소감</h2>
<p>역시 인증 관련 로직 구현은 복잡하다. firebase로 간단하게 했음에도 신경써야할게 많다는걸 알게 되었다. firebase에서도 나름 보안을 신경쓴 것 같아 안심이다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[flutter] textField에서 키보드 나타날때 위젯 위치 이동 못하게 막기]]></title>
            <link>https://velog.io/@sue0-si/flutter-textField%EC%97%90%EC%84%9C-%ED%82%A4%EB%B3%B4%EB%93%9C-%EB%82%98%ED%83%80%EB%82%A0%EB%95%8C-%EC%9C%84%EC%A0%AF-%EC%9C%84%EC%B9%98-%EC%9D%B4%EB%8F%99-%EB%AA%BB%ED%95%98%EA%B2%8C-%EB%A7%89%EA%B8%B0</link>
            <guid>https://velog.io/@sue0-si/flutter-textField%EC%97%90%EC%84%9C-%ED%82%A4%EB%B3%B4%EB%93%9C-%EB%82%98%ED%83%80%EB%82%A0%EB%95%8C-%EC%9C%84%EC%A0%AF-%EC%9C%84%EC%B9%98-%EC%9D%B4%EB%8F%99-%EB%AA%BB%ED%95%98%EA%B2%8C-%EB%A7%89%EA%B8%B0</guid>
            <pubDate>Mon, 01 Jan 2024 16:28:59 GMT</pubDate>
            <description><![CDATA[<p>textField에 값을 넣기 위해 키보드를 띄우면 textField나 textField 아래에 있는 위젯이 위로 올라가는 문제가 있었다. </p>
<h3 id="해결방법">해결방법</h3>
<p>Scaffold의 속성 중 resizeToAvoidBottomInset을 false로 두면 된다. ✨</p>
<p>resizeToAvoidBottomInset은 키보드가 나타날 때 화면의 크기를 자동으로 조절하는 동작을 제어하는데, 기본값이 true라서 키보드 크기에 따라 자동으로 위젯에 할당되는 화면 크기가 줄어든 것이다. 내 코드가 반응형으로 짜여져 있어서 이런 문제가 생긴 것 같다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[WillScope deprecated 문제 해결: PopScope]]></title>
            <link>https://velog.io/@sue0-si/WillScope-deprecated-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0-PopScope</link>
            <guid>https://velog.io/@sue0-si/WillScope-deprecated-%EB%AC%B8%EC%A0%9C-%ED%95%B4%EA%B2%B0-PopScope</guid>
            <pubDate>Mon, 25 Dec 2023 15:11:51 GMT</pubDate>
            <description><![CDATA[<p>WillScope는 현시점 deprecated되어 쓸 수는 있지만 언제 사라질지 모르는 위기에 놓여있다. 플러터에서는 WillScope 대신 PopScope를 사용하는 것을 권장하고 있다. 어떻게 PopScope를 사용해서 WillScope 코드를 수정하는지 알아보았다.</p>
<h3 id="willpopscope">WillPopScope?</h3>
<p>플러터에서 Navigator를 사용하여 화면 간의 전환을 관리하면 각각의 화면이 스택에 쌓이고, 이전 화면으로 돌아갈 수 있는  &quot;뒤로 가기&quot; 기능이 자동으로 지원된다. WillPopScope 위젯은 화면을 빠져 나갈 때 뒤로 가기 이벤트를 처리하고, 이벤트를 방지하거나 추가 작업을 수행할 수 있는 기능을 제공한다.</p>
<p>기존 코드</p>
<pre><code>WillPopScope(
  onWillPop: () async {
      if (await _controller.canGoBack()) {
        await _controller.goBack();
        return false;
    }
    return true;
  }
)
</code></pre><p>onWillPop은 WillPopScope 위젯에서 제공하는 콜백 함수로, 사용자가 뒤로 가기 버튼을 눌렀을 때 실행되는 로직을 정의하는 데 사용된다. </p>
<h3 id="popscope">PopScope</h3>
<p>PopScope에는 canPop, onPopInvoked, child 속성이 주로 쓰인다. onWillPop은 onPopInvoked</p>
<ul>
<li>canPop: 시스템 뒤로가기 기능을 막는데 쓰인다. canPop이 false면 시스템 뒤로가기를 해도 스택에 쌓인 화면이 pop되지 않는다.</li>
<li>onPopInvoked: 화면을 pop하려는 시도가 있으면 실행된다. 만약 화면이 pop됬으면 didPop이 true가 되고, 아니면 false가 되어 콜백함수가 실행되지 않는다. </li>
</ul>
<p>WillPopScope와 동일한 로직으로 코드를 구성하고 싶다면 canPop을 false로 놓으면 된다. </p>
<p>PopScope로 migration한 코드</p>
<pre><code>PopScope(
        canPop: false,
        onPopInvoked: (didPop) async {
          if(await controller.canGoBack()) {
            await controller.goBack();
          } else {
            _showBackDialog();
          }
        },</code></pre><p>canPop을 true로 놓았을 때 좋은 점에 대해서는 더 공부가 필요하다. 급하면 canPop을 false로 놓고 하면 기존의 코드를 크게 수정하지 않아도 된다. </p>
<p>더 자세한 사항은 <a href="https://api.flutter.dev/flutter/widgets/PopScope-class.html">공식문서</a>를 보자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[webview_flutter 4.0버전 사용하기]]></title>
            <link>https://velog.io/@sue0-si/webviewflutter-4.0%EB%B2%84%EC%A0%84-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@sue0-si/webviewflutter-4.0%EB%B2%84%EC%A0%84-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 25 Dec 2023 14:31:48 GMT</pubDate>
            <description><![CDATA[<p>webview_flutter가 4.0으로 업데이트되면서 많은 부분이 바뀌었다. 하지만 핵심은 선택사항이던 WebViewController를 무조건 사용해야 하는 것이다. </p>
<h3 id="webview---webviewwidget-webviewcontroller">WebView -&gt; WebViewWidget, WebViewController</h3>
<p>원래는 WebView 클래스를 사용해 그 안에 컨트롤러를 두거나 자바스크립트를 허용하는 등의 조작을 했었는데, 4.0으로 바뀌면서 WebView 클래스는 사라지고 WebViewController와 WebViewWidget 클래스를 사용해야 한다. </p>
<p>기존 코드 (4.0 이하)</p>
<pre><code>WebView(
    initialUrl: &#39;https://www.google.com&#39;,
    javascriptMode: JavascriptMode.unrestricted,
    onWebViewCreated: (controller) {
        _webViewController = controller;
    }
);</code></pre><p>4.0이상 코드</p>
<pre><code>@override
initState() {
    super.initState();
    controller = WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..loadRequest(Uri.parse(&#39;https://flutter.dev&#39;));
}

@override
Widget build(BuildContext context) {
    return Scaffold(
      body: WebViewWidget(
          controller: controller,
      ),
),</code></pre><p>WebViewWidget은 build안에, WebViewController는 build 밖의 initState안에 정의한다. </p>
<p>WebViewWidget에는 controller, gestureDetection, layoutDirection을 정의하고 WebViewController에는 setJavaScriptMode, setBackgroundColor, setNavigationDelegate, loadRequest를 정의한다. </p>
<p>정리하자면, 기존에는 WebView 클래스에 controller 없이 대부분의 webView 작업을 정의할 수 있었지만, 이제는 WebViewWidget과 WebViewController를 사용해야 하고 필요한 작업을 두 클래스를 적절히 사용해야 한다. 사용방법이 달라진 것이지 기능이 사라진 것이 아니기 때문에 어렵지 않게 코드 업데이트를 할 수 있을 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[실제 안드로이드 기기로 앱 테스트하는법 (+문제상황)]]></title>
            <link>https://velog.io/@sue0-si/%EC%8B%A4%EC%A0%9C-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EA%B8%B0%EA%B8%B0%EB%A1%9C-%EC%95%B1-%ED%85%8C%EC%8A%A4%ED%8A%B8%ED%95%98%EB%8A%94%EB%B2%95-%EB%AC%B8%EC%A0%9C%EC%83%81%ED%99%A9</link>
            <guid>https://velog.io/@sue0-si/%EC%8B%A4%EC%A0%9C-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EA%B8%B0%EA%B8%B0%EB%A1%9C-%EC%95%B1-%ED%85%8C%EC%8A%A4%ED%8A%B8%ED%95%98%EB%8A%94%EB%B2%95-%EB%AC%B8%EC%A0%9C%EC%83%81%ED%99%A9</guid>
            <pubDate>Wed, 20 Dec 2023 17:29:19 GMT</pubDate>
            <description><![CDATA[<p>안드로이드 개발자가 테스팅을 할때 만약 안드로이드 기기를 가지고 있다면 IDE에 내장된 에뮬레이터 말고 실물 기기를 사용해 앱을 테스트할 수 있다. USB 포트로 핸드폰과 PC를 연결하는 방법과 wifi를 이용해 무선으로 연결하는 방법이 있다.  </p>
<h3 id="usb-디버깅">USB 디버깅</h3>
<p>핸드폰 설정에 들어가서 개발자 옵션을 검색해 개발자 옵션을 사용중으로 바꿔야 한다. 그 다음 USB 디버깅을 키면 끝이다. </p>
<p>만약 개발자 옵션이 뜨지 않는다면 개발자 모드를 해제해야 한다. 아래 지시사항을 하나씩 해보자.</p>
<ol>
<li><p>기기의 빌드번호를 찾는다. 기기별 빌드번호는 표의 경로에 있다.
<img src="https://velog.velcdn.com/images/sue0-si/post/b2ccb71b-2fff-4f75-8fcc-e61bb1f9ec34/image.png" alt=""></p>
</li>
<li><p>You are now a developer! 메시지가 표시될 때까지 빌드 번호 옵션을 <strong>일곱 번</strong> 탭하자. (이거 은근 재밌다ㅎㅎ)</p>
</li>
</ol>
<p>이렇게 하면 개발자 옵션을 검색할 수 있을 것이다. </p>
<p>만약 이렇게 했는데 연결이 안 된다면 안드로이드 스튜디오의 <strong>Help - Troubleshoot Device Connection</strong>에 들어가 adb 서버를 재시작해주면 문제가 해결될 것이다. </p>
<h3 id="무선-디버깅">무선 디버깅</h3>
<p>만약 핸드폰의 버전이 안드로이드 11 이상이라면 무선 디버깅을 할 수 있다.
PC와 핸드폰이 같은 wifi에 연결된 상태에서 Device Manager를 실행한다. (우측 상단에 있다.) <img src="https://velog.velcdn.com/images/sue0-si/post/4567ee3a-a8bb-42e4-83f1-04f813a181f1/image.png" alt=""></p>
<p>여기서 pair using wifi를 누르면 qr코드가 뜰 것이다. 이걸 핸드폰 설정의 개발자 옵션 - 무선 디버깅에 들어가 qr코드로 기기 페어링을 눌러서 코드를 찍으면 된다. 아니면 페어링 코드를 입력해도 된다.</p>
<h3 id="문제상황">문제상황</h3>
<p>하지만, 나의 경우 USB 연결은 가능했지만 무선 연결에는 실패했다.</p>
<p><img src="https://velog.velcdn.com/images/sue0-si/post/207de836-9ed0-4ba8-b464-be8132dca106/image.png" alt=""></p>
<p>같은 와이파이를 쓰고있고, 안드로이드 14를 쓰고 있고, ide도 최신 버전인 상태인데 계속 페어링이 되지 않는다. qr을 찍어도 계속 기기 페어링 중이라는 화면에서 벗어나지 못한다. </p>
<p>참고한 stackoverflow: <a href="https://stackoverflow.com/questions/71353838/pair-new-device-over-wi-fi-not-working-in-android-studio-bumblebee">https://stackoverflow.com/questions/71353838/pair-new-device-over-wi-fi-not-working-in-android-studio-bumblebee</a></p>
<p>여기서 <code>adb connect [phone_ip]:[port]</code>를 입력해보라길래 시도했는데, adb 명령어가 안 먹어서 <code>C:\Users\사용자명\AppData\Local\Android\Sdk\platform-tools</code>를 환경변수에 등록해줬다. 다시 adb connect를 해봤는데...</p>
<p><img src="https://velog.velcdn.com/images/sue0-si/post/928ae686-7b9a-4e33-b88c-52385d2bc01a/image.png" alt=""></p>
<p><strong>안된다</strong>.ㅋㅋㅋ 페어링 거절한 적이 없는데ㅠㅠ</p>
<p>레딧에서 나와 같은 마음을 가진 사람들을 많이 볼 수 있었다. 동질감을 느끼면서 우선 USB로 디버깅을 해야겠다😭😭😭 (해결되면 글 수정해야지)
<a href="https://www.reddit.com/r/androiddev/comments/12mzccg/problem_with_wireless_debugging_in_android_studio/">https://www.reddit.com/r/androiddev/comments/12mzccg/problem_with_wireless_debugging_in_android_studio/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[테스팅의 필요성에 대한 고찰]]></title>
            <link>https://velog.io/@sue0-si/%ED%85%8C%EC%8A%A4%ED%8C%85%EC%9D%98-%ED%95%84%EC%9A%94%EC%84%B1%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B3%A0%EC%B0%B0</link>
            <guid>https://velog.io/@sue0-si/%ED%85%8C%EC%8A%A4%ED%8C%85%EC%9D%98-%ED%95%84%EC%9A%94%EC%84%B1%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B3%A0%EC%B0%B0</guid>
            <pubDate>Mon, 18 Dec 2023 16:50:36 GMT</pubDate>
            <description><![CDATA[<p>코드를 구현한 후에는 코드가 의도에 맞게 작동하는지 검증이 필요할 것이다. 지금껏 테스팅이 필요할 것이고, 현업에서도 테스팅이 자주 일어날 것으로 예상했었다. 하지만 현업에 계신 분들은 테스트 케이스를 만든 적이 거의 없고, &#39;비효율적이다&#39; 라는 이야기를 많이 하셨다. 이에 여러 테스트 방법을 알아보고 각각의 장단점을 배워보며 왜 현업에서 테스팅을 잘 하지 않는지 생각해보았다.</p>
<h3 id="단위-테스트-unit-testing">단위 테스트 (Unit Testing)</h3>
<p>특정 모듈이 의도한 대로 잘 작동하는가를 테스트하는 것이다. 다음과 같은 경우에서 단위 테스트가 특히 요구된다.</p>
<ul>
<li><strong>DB</strong><ul>
<li>스키마가 변경되는 경우</li>
<li>모델 클래스가 변경되는 경우</li>
</ul>
</li>
<li><strong>Network</strong><ul>
<li>예측한 데이터가 제대로 들어오는지 확인</li>
</ul>
</li>
<li><strong>데이터 검증</strong><ul>
<li>예측한 데이터를 제대로 처리하고 있는지 확인</li>
</ul>
</li>
</ul>
<p>단위 테스트를 하나의 모듈(기능) 구현이 끝날 때마다 진행한다면, 그 프로젝트는 TDD(Test-Driven Development)를 추구하는 것이다. TDD는 안정성은 높아지나 개발 속도가 느려질 수 있다.</p>
<h3 id="mock">Mock</h3>
<p>단위 테스트가 <strong>라이브 웹 서비스 또는 데이터베이스</strong>에서 데이터를 가져오는 클래스에 의존하는 경우, 테스트 속도가 느려지거나 예외 케이스 발생시 (ex. 서버 오류, 인터넷 연결 안됨) 테스트를 못하게 되는 문제가 발생한다. 이떄 Mock객체를 사용해 테스트하고싶은 모듈의 가짜 버전을 만들어 가짜를 테스트하면 문제를 해결할 수 있다. Mock을 할 때 Mockito라는 프레임워크가 자주 쓰인다. </p>
<p>해보면서 느낀점은 소규모 프로젝트에서는 main()에서 테스트할 수 있는 것을 굳이 Mock을 사용할 필요는 없는 것 같다. 위에서 언급한 것처럼 서버와 데이터베이스를 다루지 않는다면 테스트를 안 써도 되겠다고 생각했다. </p>
<blockquote>
<p>결론</p>
</blockquote>
<p>내가 생각하는 결론은 print찍어보거나 main에서 간단하게 테스트가 가능하다면 그 프로젝트는 TDD가 매우 비효율적일 것이다. 이런 경우에는 굳이 Mock을 활용해서 복잡하게 테스트 케이스를 짤 필요가 없다고 생각한다. 하지만, 복잡한 네트워크 통신이나 규모가 매우 큰 데이터베이스의 구조를 변경하는 등의 작업에서는 개발 중간중간 테스트를 하면서 진행하는 것이 좋겠다고 판단했다.  </p>
<p>대규모 프로젝트에서는 중간중간 테스팅을 하지 않으면 후반부의 전체 테스트 (ex. Regression)에서 QA팀의 원망을 들을 수 있을것 같다... 이건 프로젝트 총책임자나 QA팀장이 정할 일 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[플러터 개발자에게 유용한 안드로이드 스튜디오 플러그인 모음]]></title>
            <link>https://velog.io/@sue0-si/%ED%94%8C%EB%9F%AC%ED%84%B0-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%97%90%EA%B2%8C-%EC%9C%A0%EC%9A%A9%ED%95%9C-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%8A%A4%ED%8A%9C%EB%94%94%EC%98%A4-%ED%94%8C%EB%9F%AC%EA%B7%B8%EC%9D%B8-%EB%AA%A8%EC%9D%8C</link>
            <guid>https://velog.io/@sue0-si/%ED%94%8C%EB%9F%AC%ED%84%B0-%EA%B0%9C%EB%B0%9C%EC%9E%90%EC%97%90%EA%B2%8C-%EC%9C%A0%EC%9A%A9%ED%95%9C-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EC%8A%A4%ED%8A%9C%EB%94%94%EC%98%A4-%ED%94%8C%EB%9F%AC%EA%B7%B8%EC%9D%B8-%EB%AA%A8%EC%9D%8C</guid>
            <pubDate>Sun, 17 Dec 2023 16:19:29 GMT</pubDate>
            <description><![CDATA[<p>플러터로 앱개발을 하면서 유용하게 사용하는 플러그인을 정리한 글입니다. 개발하면서 계속 업데이트 예정. (유용한 플러그인 있으면 댓글로 소개해주세요!)</p>
<h3 id="dart">Dart</h3>
<ol>
<li><a href="https://plugins.jetbrains.com/plugin/12429-dart-data-class">Dart Data Class</a></li>
</ol>
<p>Dart의 named argument constructor, copyWith, toJson, fromJson 메소드를 자동완성할 수 있게 해주는 플러그인입니다. </p>
<p>설치하고 나면 generate 메뉴 (alt + insert)에 위에서 언급한 메소드 생성 기능이 업데이트되어 있습니다. </p>
<table>
<thead>
<tr>
<th>적용전</th>
<th>적용후</th>
</tr>
</thead>
<tbody><tr>
<td><img src="https://velog.velcdn.com/images/sue0-si/post/94b1e949-aa15-4346-9f86-a333075767d6/image.png" alt=""></td>
<td><img src="https://velog.velcdn.com/images/sue0-si/post/5d067885-f95a-44c1-a717-9fb1164d90e3/image.png" alt=""></td>
</tr>
<tr>
<td>---</td>
<td>---</td>
</tr>
</tbody></table>
<ol start="2">
<li><a href="https://plugins.jetbrains.com/plugin/12562-jsontodart-json-to-dart-">JsonToDart</a></li>
</ol>
<p>json을 dart코드로 바꾸는 과정을 해주는 플러그인입니다. json 구조가 복잡한 경우 플러그인을 사용하는 것이 훨씬 효율적입니다. </p>
<p>설치하면 generate 메뉴에 JsonToDart라는 기능이 추가되었을 것입니다. 눌러서 json데이터를 붙여넣고 OK를 누르면 파일에 json데이터에 대한 Dart 코드가 추가됩니다. 
<img src="https://velog.velcdn.com/images/sue0-si/post/5c147eac-8641-48d1-b332-7cc6ed208362/image.png" alt=""></p>
<h3 id="flutter">Flutter</h3>
<ol>
<li><a href="https://plugins.jetbrains.com/plugin/14442-flutter-toolkit">Flutter-Toolkit</a></li>
</ol>
<p>플러터 프로젝트 빌딩을 쉽게 만드는 플러그인입니다. build, rebuild, pub get, analyze 등의 플러터 관련 명령을 버튼 한 번의 클릭으로 간편하게 할 수 있습니다. </p>
<p>설치하면 generate 메뉴와 android studio 상단 툴바에 기능이 추가됩니다. 총 8가지 기능 - Kill Gradle, Kill Flutter, Analyze, Pub get, Build, Rebuild, Watch, Clean을 지원합니다. 
<img src="https://velog.velcdn.com/images/sue0-si/post/bcff9f35-d40b-4c97-aad2-a38611a09675/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Dart 네트워크 기초]]></title>
            <link>https://velog.io/@sue0-si/Dart-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@sue0-si/Dart-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Thu, 14 Dec 2023 16:21:28 GMT</pubDate>
            <description><![CDATA[<p>이 글에서는 Dart에서 어떻게 json 데이터를 전송하고 교환하는지에 대해 알아보겠다.</p>
<h3 id="1-데이터를-받아올-객체를-모델-클래스로-만들기">1. 데이터를 받아올 객체를 모델 클래스로 만들기</h3>
<p>json 데이터를 클라이언트로 받아올 때 이를 모델 클래스로 만들어서 다양하게 데이터가 쓰일 수 있게 한다.</p>
<p>하지만, json 데이터 안의 key값이 너무 많거나 key가 리스트 형태라면 수동으로 모델 클래스를 만드는 것이 어려울 것이다. 그렇다면 <a href="https://javiercbk.github.io/json_to_dart/">Json to Dart 사이트</a>에서 json 데이터를 넣어주고 원하는 모델 클래스 이름을 넣어주면 모델 클래스가 만들어진다!</p>
<h3 id="2-json-1개-파싱">2. json 1개 파싱</h3>
<p>파싱이란 json 데이터를 원하는 프로그래밍 언어나 환경에서 읽어들이고 해당 데이터를 사용할 수 있도록 하는 과정이다.</p>
<p>만약 json을 1개만 모델 클래스로 파싱하려면 아래와 같은 패턴으로 코드를 구현하면 된다. </p>
<pre><code class="language-dart">Future&lt;Todo&gt; getTodo(int id) async {
  // 요청
  final response = await http.get(Uri.parse(&#39;https://jsonplaceholder.typicode.com/todos/$id&#39;));
  // json String
  final jsonString = response.body;
  // toMap
  final json = jsonDecode(jsonString);
  // 모델클래스로 변환
  return Todo.fromJson(json);
}</code></pre>
<h3 id="3-json-array를-파싱">3. json array를 파싱</h3>
<p>여러개의 json데이터를 파싱할 때 사용되는 패턴이다. </p>
<pre><code class="language-dart">Future&lt;List&lt;Todo&gt;&gt; getTodos() async {
  final response = await http.get(Uri.parse(&#39;https://jsonplaceholder.typicode.com/todos/&#39;));
  final json = jsonDecode(response.body) as List&lt;dynamic&gt;;
  return json.map((e) =&gt; Todo.fromJson(e)).toList();
}</code></pre>
<h3 id="느낀점">느낀점</h3>
<p>json을 다루는 것이 익숙하지 않아서 버벅임이 있다. 파싱을 왜 해야하는지 이해했지만 이를 코드로 구현하고 패턴을 응용하는 과정이 낯설다. 계속 코드도 짜보고 예제도 보면서 조금씩 감각을 익혀나가야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Dart 비동기 프로그래밍]]></title>
            <link>https://velog.io/@sue0-si/Dart-%EB%B9%84%EB%8F%99%EA%B8%B0-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</link>
            <guid>https://velog.io/@sue0-si/Dart-%EB%B9%84%EB%8F%99%EA%B8%B0-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D</guid>
            <pubDate>Wed, 13 Dec 2023 16:23:33 GMT</pubDate>
            <description><![CDATA[<p>비동기 처리에 대해 배운 후 문제를 풀다가 막히는 부분이 있어 정리해보는 글.</p>
<pre><code class="language-dart">Future&lt;String&gt; timeoutFuture() async {
  await Future.delayed(Duration(seconds: 6));
  return &#39;ok&#39;;
}
void main() async {
  print(timeoutFuture());
}</code></pre>
<p>위 코드에서 나는 6초 뒤에 ok를 출력하고 싶었다. 하지만, <code>Instance of Future&lt;String&gt;</code>이라고 출력되었다. 이 문제를 어떻게 해결할까? Future와 async-await 개념을 이해하면 문제가 보일 것이다.</p>
<h3 id="future">Future</h3>
<p>자바스크립트의 Promise와 같은 개념으로, 미래에 받아올 값이라는 것을 명시해주는데 쓰인다. Future를 사용할 떄 async를 꼭 사용해줘야 하는데, Future는 비동기 처리가 필수이기 때문이다. </p>
<h3 id="async-await">async-await</h3>
<p>async로 정의된 비동기 함수를 실행할때, 꼭 await 키워드를 사용해야 코드가 작성된 순서대로 실행된다. </p>
<p><code>Future&lt;void&gt; test() async {
    await Future.delayed(Duration(seconds: 5));
    print(&quot;Hello&quot;);
}</code></p>
<p>이 코드에서 test를 호출했을때, 5초 있다가 Hello 메세지를 출력하려면 delayed에 await를 사용해야 한다. 단순히 delayed만 썼다고 비동기 처리가 되는 것이 아니다. </p>
<h3 id="문제-해결">문제 해결</h3>
<p>코드의 문제는 timeoutFuture()를 출력할때 await을 사용하지 않았기 때문이다. timeoutFuture()은 비동기 함수이기 때문에 호출이 되면 바로 Future 객체가 반환된다. 그래서 await을 사용해 Future 객체가 완료될때까지 기다리게 해야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Dart iterable 클래스의 유용한 메소드들]]></title>
            <link>https://velog.io/@sue0-si/Dart-iterable-%ED%81%B4%EB%9E%98%EC%8A%A4%EC%9D%98-%EC%9C%A0%EC%9A%A9%ED%95%9C-%EB%A9%94%EC%86%8C%EB%93%9C%EB%93%A4</link>
            <guid>https://velog.io/@sue0-si/Dart-iterable-%ED%81%B4%EB%9E%98%EC%8A%A4%EC%9D%98-%EC%9C%A0%EC%9A%A9%ED%95%9C-%EB%A9%94%EC%86%8C%EB%93%9C%EB%93%A4</guid>
            <pubDate>Tue, 12 Dec 2023 16:28:49 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>where</p>
</blockquote>
<p>조건에 부합하는 것만 가져오는 메소드이다. </p>
<pre><code>transactions.where((e) =&gt; e.trader.city == &#39;Cambridge&#39;);

    // 아래 if문과 동일
    List&lt;String&gt; result = [];
    for (int i = 0; i &lt; transactions.length; i++) {
        if (transactions[i].trader.city == &#39;Cambridge&#39;) {
            result.add(transactions[i]);
        }
    }</code></pre><blockquote>
<p>map </p>
</blockquote>
<p>값을 정의한 값으로 변환해준다.</p>
<pre><code>transactions.map((e) =&gt; e.trader.city);</code></pre><blockquote>
<p>forEach</p>
</blockquote>
<p>for loop과 기능이 비슷하다. for loop을 한 줄로 표현 가능하게 해준다.</p>
<blockquote>
<p>reduce</p>
</blockquote>
<p>e를 받아서 리턴 코드를 실행한 결과를 v에 넣고, 이 과정을 반복한다.</p>
<pre><code>transactions.map((e) =&gt; e.value)
                  .reduce((v, e) =&gt; max(v, e));</code></pre><blockquote>
<p>any</p>
</blockquote>
<p>리턴코드에 정의된 사항에 부합하는 것이 있는지 없는지 확인해준다. 리턴타입은 bool이다.</p>
<pre><code>transactions.any((e) =&gt; e.trader.city == &#39;Milano&#39;);</code></pre><blockquote>
<p>toList()</p>
</blockquote>
<p>함수형 프로그래밍을 사용하면서 리턴 타입인 Iterable 타입을 리스트 타입으로 바꿔준다.</p>
<pre><code>   transactions.where((e) =&gt; e.trader.city == &#39;Cambridge&#39;)
          .toList();</code></pre><blockquote>
<p>toSet()</p>
</blockquote>
<p>Iterable 타입을 Set 타입으로 바꿔준다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Dart 기본 문법 (9) - 파일 조작, 데이터 형식]]></title>
            <link>https://velog.io/@sue0-si/Dart-%EA%B8%B0%EB%B3%B8-%EB%AC%B8%EB%B2%95-9-%ED%8C%8C%EC%9D%BC-%EC%A1%B0%EC%9E%91-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%98%95%EC%8B%9D</link>
            <guid>https://velog.io/@sue0-si/Dart-%EA%B8%B0%EB%B3%B8-%EB%AC%B8%EB%B2%95-9-%ED%8C%8C%EC%9D%BC-%EC%A1%B0%EC%9E%91-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%98%95%EC%8B%9D</guid>
            <pubDate>Mon, 11 Dec 2023 16:01:52 GMT</pubDate>
            <description><![CDATA[<h2 id="파일조작">파일조작</h2>
<p>간단하게만 알아보자면 3단계로 구성된다.</p>
<ol>
<li>파일을 연다 ⇒ <code>final fileName = File(&#39;파일.txt&#39;);</code></li>
<li>파일을 읽거나 쓴다 ⇒ 읽기: <code>fileName.writeAsStringSync(&#39;Hello World&#39;);</code>
쓰기: <code>fileName.readAsStringSync();</code></li>
<li>파일을 닫는다 ⇒ Dart에서는 dart:io 라이브러리가 자동으로 파일을 닫아주기 때문에 따로 코드를 작성할 필요는 없지만, dart:io가 비동기적이기 때문에 파일 작업을 할 때는 비동기로 처리해야 한다는 것을 알아두자.</li>
</ol>
<pre><code class="language-dart">import &#39;dart:io&#39;;

void main() {
  void copy (String source, String target) {
    final sourceFile = File(source);
    final targetFile = File(target);

    sourceFile.writeAsStringSync(&quot;졸음껌과 박하사탕&quot;);

    final text = sourceFile.readAsStringSync();
    targetFile.writeAsStringSync(text);
  }

  copy(&quot;source.txt&quot;, &quot;target.txt&quot;);
}</code></pre>
<h2 id="데이터-형식">데이터 형식</h2>
<p>: 대부분 라이브러리를 지원하니 적극 활용하자 -&gt;  <a href="https://pub.dev/packages">https://pub.dev/packages</a> </p>
<h3 id="csv">csv</h3>
<p>데이터를 콤마로 나눈 형식으로, 엑셀 데이터와 비슷하다.
ex) <code>String str = “홍길동”, ”한석봉”, “신사임당”;</code></p>
<h3 id="properties">properties</h3>
<p>key-value 쌍으로 읽고 쓰기를 가능하게 하는 클래스이다. 설정 정보를 관리하고 읽어오기 위해 사용된다.</p>
<pre><code class="language-dart">    # 예시 프로퍼티 파일
    database.url=jdbc:mysql://localhost:3306/mydb
    database.username=myuser
    database.password=mypassword</code></pre>
<h3 id="xml">XML</h3>
<p>&lt;&gt; 태그를 활용한 기술 방식으로 포함관계를 기술할 수 있다. HTML을 다뤄봤다면 익숙할 것이다. 하지만 DOM Parser, SAX Parser 등을 통해 파서를 따로 제작해야 해서 잘 쓰이지 않는다.</p>
<h3 id="json">json</h3>
<p>네트워크 통신에서 가장 많이 사용되고, XML에 비해 적은 용량을 차지한다. []로 리스트, {}로 객체를 구현한다. key-value 쌍으로 데이터가 표현된다. 데이터를 넣을때 꼭 <strong>큰 따옴표</strong>를 사용하자!</p>
<pre><code class="language-dart">final json = {
    &quot;name&quot; = &quot;Bubble Gum&quot;,
    &quot;age&quot; = 32,
};</code></pre>
<h4 id="직렬화">직렬화</h4>
<p>데이터를 일련의 바이트 또는 다른 형식으로 변환하는 과정으로, Dart에서는 <strong>클래스 내용을 Json 형태로 변환하는 것</strong>으로 본다.
<code>toJson()</code>으로 객체를 Json 형태로 표현한다</p>
<pre><code>    Map&lt;String, dynamic&gt; toJson() =&gt; {
        &quot;name&quot; : name,
        &quot;age&quot; : age,
    }</code></pre><p><code>jsonEncode()</code> : Map을 json형식의 String으로 변환해준다. 단순히 toJson()을 쓰면 큰 따옴표가 들어가지 않은 형식의 String으로 리턴되어 나중에 데이터를 다시 json화할 수 없다.</p>
<h4 id="역직렬화">역직렬화</h4>
<p>직렬화된 데이터를 다시 원래의 데이터 구조로 변환하는 과정으로, <strong>Json 형태를 클래스 내용으로 변환하는 것</strong>이다. <code>fromJson()</code> 생성자로 json을 객체로 만든다.</p>
<pre><code class="language-dart">User.fromJson(Map&lt;String, dynamic&gt; json)
    : name = json[&quot;name&quot;],
        age = json[&quot;age&quot;]; </code></pre>
<p><code>jsonDecode()</code> : String 형태의 Json을 Map으로 변환할 때 사용된다. </p>
<blockquote>
<p>느낀점</p>
</blockquote>
<p>여기서부터는 객체지향과 살짝 멀어진 개념을 배우는 것 같다. 자바스크립트에서 자주 사용되는데 앱개발에서도 쓰이는 것 같아 신기했다. json은 서버와 클라이언트간의 소통을 위해 쓰인다고 하는데, 나중에 백엔드와 상호작용할 때 잘 활용할 수 있도록 많이 복습해둬야겠다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Dart 기본 문법 (8) - 제네릭, enum, String 클래스]]></title>
            <link>https://velog.io/@sue0-si/Dart-%EA%B8%B0%EB%B3%B8-%EB%AC%B8%EB%B2%95-8-%EC%A0%9C%EB%84%A4%EB%A6%AD-enum-String-%ED%81%B4%EB%9E%98%EC%8A%A4</link>
            <guid>https://velog.io/@sue0-si/Dart-%EA%B8%B0%EB%B3%B8-%EB%AC%B8%EB%B2%95-8-%EC%A0%9C%EB%84%A4%EB%A6%AD-enum-String-%ED%81%B4%EB%9E%98%EC%8A%A4</guid>
            <pubDate>Fri, 08 Dec 2023 14:52:39 GMT</pubDate>
            <description><![CDATA[<h2 id="제네릭-generics">제네릭 (Generics)</h2>
<p>변수를 정의할 때 타입을 정하지 못하는 순간이 있다. 이때 그냥 var, const, final 등의 키워드로 타입을 정의하지 않고 지나가면 IDE가 컴파일 에러를 발견하지 못해 후에 런타임 에러가 나기 쉽다. </p>
<p>이러한 불상사를 방지하기 위해 제네릭을 사용해서 타입을 나중에 원하는 형태로 정의할 수 있다. 즉, type safe 효과를 위해 제네릭을 사용한다. </p>
<pre><code class="language-dart">class StrongBox&lt;E&gt; {
  E? _money;

  void get(E money) {
    _money = money;
  }
}

void main() {
  StrongBox box = StrongBox();
  box.get(&quot;10000&quot;);
}</code></pre>
<p>위 코드에서는 <code>_money</code> 변수에 &quot;10000&quot;이라는 String 값을 넣었고, 이 순간 <code>_money</code>의 타입은 String이 된다. 여기서 int형 10000을 넣으면 변수의 타입은 int가 될 것이다. 이처럼 제네릭은 임시 자료형이라고 생각하면 된다. </p>
<p>또한, <code>extends</code>를 사용해서 특정 타입만 E의 타입이 될 수 있도록 제한할 수 있다. 만약<code>&lt;E extends String&gt;</code>였다면 <code>_money</code>에 숫자 10000을 넣을 수 없을 것이다. </p>
<h2 id="열거형-enum">열거형 (enum)</h2>
<p>상수(const)의 집합 클래스이다. 이 클래스에 정의된 값들은 변하지 않는다. 각각의 상수에 대한 처리가 필요할 때 switch문을 사용한다. if문을 사용하지 않는 이유는 상수가 추가될 때 if문에 조건을 추가하지 않는 실수를 할 수 있는 반면 switch에서는 각각의 상수에 대한 처리를 하지 않으면 에러가 나므로 실수를 할 가능성을 줄인다. </p>
<pre><code class="language-dart">enum KeyType {
  padlock,
  button,
  dial,
  finger
}</code></pre>
<h2 id="string-클래스">String 클래스</h2>
<h3 id="문자열-결합-방법">문자열 결합 방법</h3>
<h4 id="-연산">+ 연산</h4>
<p>합치고자 하는 문자열 사이에 +를 넣는 방법이다. 가장 직관적이지만 계속 +로 문자열을 합치는 경우 합쳐진 결과물이 새로운 인스턴스로 메모리에 계속 할당되기 때문에 문자열이 만들어지는 속도가 느리다. 
ex) <code>print(&#39;Hello&#39; + &#39;World&#39;)</code></p>
<h4 id="string-interpolation">string interpolation</h4>
<p>문장에 변수를 넣어야 할 때 많이 사용되어지는 방법이다. ${수식} 형태로 + 기호 없이 변수를 문장에 바로 넣을 수 있다. </p>
<h4 id="stringbuffer">StringBuffer</h4>
<p>write() 메소드로 결합한 결과를 내부 메모리(버퍼)에 담아두고 toString으로 결과를 얻는 방법이다.</p>
<pre><code class="language-dart">final buffer = StringBuffer(&#39;Dart&#39;);
buffer 
    ..write(&#39; and &#39;)
    ..write(&#39;Flutter&#39;);
print(buffer.toString());  // Dart and Flutter</code></pre>
<p>위 코드에서 <code>..</code>연산자는 cascade연산자로도 불리며, void 리턴인 함수의 앞에 사용하면 해당 객체의 레퍼런스를 반환하여 메서드 체인을 사용할 수 있게 해준다. <code>buffer..write(&#39; and &#39;)</code>는<code>buffer.write(&#39; and &#39;)</code>와 같은 코드이다. </p>
<h3 id="string-클래스의-유용한-메소드들">String 클래스의 유용한 메소드들</h3>
<ul>
<li>substring(int a, (int b)): 인덱스 a부터 b 전까지의 문자열 리턴. 원본을 바꾸지 않는다. </li>
<li>replaceAll(Pattern a, String b): 문자열 중 모든 a값을 b로 바꾼다. mutator이기 때문에 원본을 바꾸지 않는다. </li>
</ul>
<pre><code class="language-dart">void main() {
  String greeting = &#39;Hello&#39;;

  print(greeting.replaceAll(&#39;H&#39;, &#39;J&#39;));
  print(greeting);
}

// output: Jello
//                 Hello</code></pre>
<ul>
<li><p>split(Pattern pattern):  pattern에 따라 문자열을 나누어 리스트로 리턴한다.</p>
</li>
<li><p><code>indexOf(Pattern pattern, (int start))</code>:  (start 인덱스부터) pattern이 어느 인덱스에 있는지 알려준다. 없다면 -1을 리턴한다.</p>
<pre><code class="language-dart">  final string = &#39;HELLO&#39;;
  print(string.indexOf(&#39;E&#39;));  // output: 1</code></pre>
</li>
<li><p><code>length</code> : 문자열의 길이를 리턴한다.</p>
</li>
<li><p><code>isEmpty</code> : 길이가 0인지 아닌지를 알려준다.</p>
</li>
<li><p><code>contains(String a)</code> : a가 문자열에 포함되어 있는지 알려준다.</p>
</li>
<li><p><code>endsWith()</code> &amp; <code>startsWith()</code> : 문자열이 ~로 끝나는지 / 시작하는지를 알려준다.</p>
</li>
<li><p><code>indexOf()</code> : 단어가 몇 번째에 있는지를 알려준다.</p>
</li>
<li><p><code>lastIndexOf()</code> : 뒤에서 몇 번째에 단어가 있는지를 알려준다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Dart 기본 문법 (7) - Object 클래스]]></title>
            <link>https://velog.io/@sue0-si/Dart-%EA%B8%B0%EB%B3%B8-%EB%AC%B8%EB%B2%95-7-Object-%ED%81%B4%EB%9E%98%EC%8A%A4</link>
            <guid>https://velog.io/@sue0-si/Dart-%EA%B8%B0%EB%B3%B8-%EB%AC%B8%EB%B2%95-7-Object-%ED%81%B4%EB%9E%98%EC%8A%A4</guid>
            <pubDate>Thu, 07 Dec 2023 16:10:36 GMT</pubDate>
            <description><![CDATA[<h2 id="인스턴스-기본-조작">인스턴스 기본 조작</h2>
<p>모든 클래스는 Object 클래스의 메소드와 프로퍼티를 가지고 있다. Object 타입 변수에는 모든 인스턴스를 대입할 수 있다.</p>
<p>Object 클래스의 대표 메서드 및 프로퍼티에는 toString(), <code>==</code> operator, hashCode가 있다.</p>
<h3 id="tostring">toString()</h3>
<p>객체의 문자열 표현을 반환하게 해준다. 이 메소드를 구현하지 않으면 기본으로 객체는 <code>Instance of 클래스이름</code> 형태로 출력된다. 이는 보기 좋은 표현이 아니기 때문에 toString()을 재정의해서 원하는 방식으로 객체를 출력할 수 있게 해준다. 즉, 객체를 더 현실세계의 상황에 맞게 출력할 수 있게 해준다.</p>
<h3 id="-연산자">== 연산자</h3>
<p>객체의 reference를 비교하는데 쓰인다. 자바의 equals 메소드와 기능이 같으며, 객체의 주소값뿐 아니라 프로퍼티까지 같은지 알려준다. </p>
<p>객체의 프로퍼티 하나하나가 다 같으면 두 객체가 같다라고 정의하고 싶으면 프로퍼티에 대한 동등성을 검사하는 코드를 써줘야 한다.</p>
<pre><code class="language-dart">@override
  bool operator == (Object other) =&gt;
      identical(this, other) ||
          other is Book &amp;&amp;
              runtimeType == other.runtimeType
              &amp;&amp; title == other.title
              &amp;&amp; publishDate == other.publishDate;</code></pre>
<h3 id="sort">sort()</h3>
<p><code>List.sort()</code> 메소드는 컬렉션 내부를 정렬해준다. 하지만, sort 메소드를 사용하려면 Comparator를 구현해야 한다.</p>
<pre><code class="language-dart">final names = [&#39;Seth&#39;, &#39;Kathy&#39;, &#39;Lars&#39;];
names.sort((a,b) =&gt; a.compareTo(b));
print(names);</code></pre>
<p>우선 클래스에서 comparable 인터페이스를 implement 받고, compareTo 메소드를 구현해야 한다. compareTo메소드는 a, b 두개의 인자를 받고 다음 규칙대로 리턴한다.</p>
<ul>
<li>a 가 b 보다 작으면 음수 (-1)</li>
<li>같으면 0</li>
<li>a 가 b 보다 크면 양수 (+1)</li>
</ul>
<h3 id="deep-copy">Deep copy</h3>
<pre><code>Book book1 = Book(&#39;Harry Potter&#39;);
Book book2 = book1;</code></pre><p>shallow copy의 예시이다. 만약 book2.title = ‘Lord of Rings’ 로 한다면? person1.title도 ‘Lord of Rings’로 바뀐다… 이는 두 객체가 같은 주소를 사용하고 있기 때문이다.</p>
<p>이런 문제를 방지하려면 deep copy를 사용하여 새로운 주소를 갖는 객체에 값을 복사해와야 한다. 하지만, 다트에서는 deep copy를 따로 지원하지 않아 아래와 같은 메소드를 따로 구현해줘야 한다.</p>
<pre><code class="language-dart">Book copyWith({
    String? title,
  }) {
    return Book(
      title: title ?? this.title,
    );
  }</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Dart 기본 문법 (6) - 다형성]]></title>
            <link>https://velog.io/@sue0-si/Dart-%EA%B8%B0%EB%B3%B8-%EB%AC%B8%EB%B2%95-6-%EB%8B%A4%ED%98%95%EC%84%B1</link>
            <guid>https://velog.io/@sue0-si/Dart-%EA%B8%B0%EB%B3%B8-%EB%AC%B8%EB%B2%95-6-%EB%8B%A4%ED%98%95%EC%84%B1</guid>
            <pubDate>Wed, 06 Dec 2023 15:24:20 GMT</pubDate>
            <description><![CDATA[<p>같은 부모를 가지는 다른 인스턴스를 동일시하는 것이다. 상속에 의한 is-a관계가 성립하면 인스턴스를 부모 클래스 타입의 변수에 대입할 수 있다. 또한, 클래스를 인터페이스로 선언하면 인터페이스도 타입이 될 수 있다. </p>
<pre><code class="language-dart">abstract interface class Drawable {
    void draw();
}

void main() {
    Drawable element = House();
    List&lt;Drawable&gt; paintings = [];
    paintings.add(Dog());
}
</code></pre>
<p>상속관계, 인터페이스 구현 관계에 있을 때만 다형성을 사용할 수 있다. 선언은 상위 개념으로, 인스턴스 생성은 하위 개념으로 한다. </p>
<pre><code class="language-dart">void main() {
    Student student1 = Freshman();
    Freshman freshman = student1 as Student;
}</code></pre>
<p>위 코드에서 student1의 타입은 Student지만, 실체는 Freshman 클래스의 인스턴스이다. 타입은 어떤 멤버를 접근할 수 있는지를 결정하고, 실체는 멤버가 어떻게 움직이는지를 결정한다. 즉, 타입 클래스에 원하는 멤버가 정의되어 있는지 확인하고 실체인 클래스에서 원하는 멤버를 불러오면 된다. </p>
<p>타입 캐스팅은 <code>as</code>로 하면 되지만, 런타임에 하기 때문에 위험할 수 있다.
<code>(character as Wizard).fireball(slime)</code>
 -&gt; 그래서 <code>is</code>로 타입 체크를 하는 것이 좋다!</p>
<pre><code class="language-dart">if (character is Wizard) {
    character.fireball(slime);
}</code></pre>
<p>Dart에서는 메소드 오버로딩을 지원하지 않지만, 다형성을 활용해서 더 큰 개념을 메소드의 인자로 활용하면 문제를 해결할 수 있다. </p>
<h3 id="정리">정리</h3>
<p>다형성은 같은 부모를 가지는 다른 인스턴스를 동일시하여, 부모 클래스 타입에 자식 클래스의 인스턴스를 담는 것이다. 동일시 취급해도 각각의 인스턴스는 각 클래스의 정의를 따르고 다른 동작을 한다. 부모 클래스 타입의 인수나 리턴값을 이용하여 다른 클래스를 모아서 처리 가능하다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Dart 기본 문법 (5) - 상속, 추상클래스, 인터페이스]]></title>
            <link>https://velog.io/@sue0-si/Dart-%EA%B8%B0%EB%B3%B8-%EB%AC%B8%EB%B2%95-4-%EC%83%81%EC%86%8D-%EC%B6%94%EC%83%81%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4</link>
            <guid>https://velog.io/@sue0-si/Dart-%EA%B8%B0%EB%B3%B8-%EB%AC%B8%EB%B2%95-4-%EC%83%81%EC%86%8D-%EC%B6%94%EC%83%81%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4</guid>
            <pubDate>Tue, 05 Dec 2023 15:52:29 GMT</pubDate>
            <description><![CDATA[<h2 id="상속">상속</h2>
<p>이전에 만든 클래스에서 추가 기능을 덧붙일때 사용된다. 부모 클래스의 멤버는 자동적으로 자식 클래스에 상속되므로, 자식 클래스에는 추가 된 부분만 기술 하면 된다. 그렇기에 같은 기능을 복사 붙여넣기하지 않아도 되는 것이다. 자식 클래스는 더 구체적이고, 부모 클래스는 더 추상적 / 일반적인 개념을 갖는다.자식 클래스에서 <code>extends</code> 키워드를 사용해서 구현된다. </p>
<p>부모 클래스의 메소드를 사용하려면 override해야 한다. 이때 @override 애노테이션을 붙여야 한다.</p>
<pre><code class="language-dart">@override 
void run() {
    print(&quot;Ahh&quot;);
}</code></pre>
<p>부모 클래스의 객체를 참조하려면 <code>super</code> 키워드를 사용해야 한다.</p>
<p>ex) <code>SuperHero({required super.name, required super.hp});</code></p>
<p>부모 클래스가 생성자가 있다면 자식 클래스에서 부모 클래스의 생성자를 상속받아야 한다. 부모 생성자가 먼저 실행되고, 다음 자식 생성자가 실행된다.</p>
<p>다트에서 다중상속은 불가능하지만 Mixin으로 다중상속의 느낌을 낼 수 있다. </p>
<p>상속은 is-a 원칙으로 사용하면 된다. 
<code>a(자식클래스)는 b(부모클래스)이다</code> 라는 말이 성립하면 옳은 관계이다.</p>
<p>현실 세계의 등장인물 사이에 개념적으로 is-a 관계가 성립하지 않는데 상속을 사용하면 잘못된 것이다. 이러면 클래스를 확장할 때 현실세계와 모순이 생기고, 다형성을 이용할 수 없게 된다. </p>
<h2 id="추상클래스">추상클래스</h2>
<p>상속의 재료로 사용되며, 상세 부분이 정의되지 않아 자식 클래스에서 무조건 구현해야 한다. 즉, 자식 클래스에서 override가 강제된다. </p>
<p><code>abstract</code> 키워드를 붙이면 추상클래스 / 메소드가 된다. 추상 클래스는 인스턴스화될 수 없고, 추상 메소드는 method body를 가질 수 없다.</p>
<p>추상클래스는 뜻하지 않은 인스턴스화나 오버라이드를 구현하지 않는 문제를 예방할 수 있다.</p>
<h2 id="인터페이스">인터페이스</h2>
<p>Dart3에 추가된 기능으로, 추상 클래스 중 추상 메소드만 가지고 있는 것이라고 생각해도 된다. 인터페이스의 모든 메소드는 추상 메소드이고, 필드를 갖지 않는다. 또한, 여러 인터페이스를 implement할 수 있기 때문에 다중상속의 효과를 볼 수 있다. extends와 implements를 동시에 사용할 수 있다. </p>
<p>같은 인터페이스를 구현한 클래스들은 공통 메소드를 구현하고 있음을 보장하며, 어떤 클래스가 인터페이스를 구현하고 있다면 적어도 그 인터페이스에 정의된 메소드를 가지고 있다는 것이 보증된다.</p>
<pre><code class="language-dart">abstract interface class 클래스이름 {
    void 메소드();
}</code></pre>
<h2 id="정리">정리</h2>
<p>상속은 부모 클래스의 모든 것을 자식 클래스에서 복붙한다음 내용을 추가하는 것이다.</p>
<p>추상클래스와 인터페이스는 템플릿이다. 템플릿을 만들어 놓으면 이걸 가져와서 각자 다르게 내용을 채우는 것이다. 틀을 만들어 놓기 때문에 개발을 효율적으로 할 수 있게 하고, 구현을 강제하기 때문에 코드의 안전성을 확보할 수 있다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Dart 기본 문법 (4) - 캡슐화]]></title>
            <link>https://velog.io/@sue0-si/Dart-%EA%B8%B0%EB%B3%B8-%EB%AC%B8%EB%B2%95-4-%EC%BA%A1%EC%8A%90%ED%99%94</link>
            <guid>https://velog.io/@sue0-si/Dart-%EA%B8%B0%EB%B3%B8-%EB%AC%B8%EB%B2%95-4-%EC%BA%A1%EC%8A%90%ED%99%94</guid>
            <pubDate>Mon, 04 Dec 2023 15:15:43 GMT</pubDate>
            <description><![CDATA[<h2 id="캡슐화-encapsulation">캡슐화 (encapsulation)</h2>
<p>다른 파일, 클래스, 메소드 등에서 특정 변수, 메소드, 클래스에 접근할 수  있는 권한을 부여하거나 하지 않는것이다. 엑세스 제어, getter &amp; setter로 할 수 있다. </p>
<p>다른 곳에서 사용하지 못하게 변수나 메소드, 또는 클래스 자체를 캡슐에다 넣어놓아 외부에서 내용물이 보이지 않게 하는 느낌이다 (아래 이미지를 참조하자). </p>
<p><img src="https://velog.velcdn.com/images/sue0-si/post/a79ce72a-e738-4b4b-ad25-3111ff2ce3b5/image.png" alt=""></p>
<p>캡슐화는 개발자의 잘못된 접근에 대한 제어방법을 제공한다. 즉, 필드에 “현실세계에서 불가능한 값&quot;이 들어가지 않도록 제어한다. 그러므로 캡슐화는 실수로 속성을 덮어 쓰거나, 잘못된 조작을 하는 등의 휴먼 에러를 사전에 방지하기 위해 사용된다.</p>
<h3 id="접근-지정자-access-modifier">접근 지정자 (access modifier)</h3>
<p>접근 범위를 지정할 때 사용된다. 디폴트는 public, 즉 모든 클래스에서 접근 가능하지만 멤버 앞에 <code>_</code>을 붙이면 그 멤버는 속한 클래스에서만 접근할 수 있는 private 멤버가 된다. </p>
<p>ex) <code>int _hp = 10;</code>
ex) <code>void _die() { }</code></p>
<p>클래스 자체도 private으로 설정될 수 있다. private class는 자신의 인스턴스가 생성되지 않는다. </p>
<pre><code class="language-dart">class _sum { 
    // 코드...
}

void main() {
    _sum number1 = _sum();  // 불가능
}</code></pre>
<h3 id="getter--setter-메소드">getter / setter 메소드</h3>
<p>private 변수에 접근 / 수정하기 위해서 사용된다. 이 메소드들을 경유해서 private 필드에 접근하는 방식이다.</p>
<ul>
<li>getter: 읽기 전용 프로퍼티를 구현할 때 사용</li>
<li>setter: 쓰기 전용 프로퍼티를 구현할 때 사용 (잘 쓰지 않는다고 한다)</li>
</ul>
<h4 id="getter--setter를-사용하는-이유">getter / setter를 사용하는 이유</h4>
<ol>
<li>Read Only, Write Only 필드의 실현</li>
<li>필드의 이름 등, 클래스의 내부 설계를 자유롭게 변경 가능</li>
<li>필드로의 액세스를 검사 가능</li>
</ol>
<pre><code class="language-python">int get hp =&gt; _hp;

// 값의 타당성 검사
void set hp(int value) {
    vehicleAge -= value;
    if (value &lt; 0) {
        throw Exception(&quot;hp는 0보다 작을 수 없다&quot;);
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Dart 기본 문법 (3) - 클래스, 생성자]]></title>
            <link>https://velog.io/@sue0-si/Dart-%EA%B8%B0%EB%B3%B8-%EB%AC%B8%EB%B2%95-3-%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%83%9D%EC%84%B1%EC%9E%90</link>
            <guid>https://velog.io/@sue0-si/Dart-%EA%B8%B0%EB%B3%B8-%EB%AC%B8%EB%B2%95-3-%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%83%9D%EC%84%B1%EC%9E%90</guid>
            <pubDate>Mon, 04 Dec 2023 00:00:22 GMT</pubDate>
            <description><![CDATA[<h3 id="클래스">클래스</h3>
<p>클래스는 객체를 가상세계 용으로 구체화한 것이다. 클래스를 정의하면 <strong>인스턴스를 생성</strong>할 수 있게 된다. 인스턴스는 클래스를 활용해 메모리에 저장되는 것이다. 또한, <strong>클래스 고유의 변수 타입을 사용</strong>할 수 있게 된다. </p>
<p>즉, 클래스는 인스턴스를 생성하기 위한 틀이 클래스인 것이다. 😊😊</p>
<h4 id="인스턴스-생성">인스턴스 생성</h4>
<p>대부분의 객체지향 언어에서는 new 키워드로 클래스의 인스턴스를 생성하지만, Dart에서는 new를 생략 가능하다. 생성된 인스턴스를 통해 클래스의 필드와 메소드를 이용할 수 있게 되는 것이다. </p>
<pre><code class="language-dart">class College {
       late String name;
    late int age;
    void enrollStudent() {
        name = &#39;meme&#39;;
        age = 22;
    }

}

void main() {
    College myUniv = College();
    myUniv.enrollStudent();
    print(&#39;${myUniv.name}  ${myUniv.age}&#39;);   // output: meme  22
}</code></pre>
<p>위 코드에서 myUniv는 College 타입으로, College 클래스의 필드와 메소드를 사용하려면 College 타입의 인스턴스를 사용해야 한다. </p>
<h4 id="premitive-type-vs-reference-type">premitive type vs. reference type</h4>
<p>자바에서는 int, double, float 등이 premitive type이고 string, object는 reference type인데 Dart에서는 <strong>모든 타입이 reference type</strong>이다. 즉, 모든 변수에 주소가 할당되는 것이다.</p>
<h4 id="naming-convention">Naming Convention</h4>
<p>클래스 이름은 명사로, 첫 글자를 대문자로 지으면 된다. ex) College, Human
필드 이름은 명사로, 두번째 단어부터 첫 글자를 대문자로 짓는다 (lowerCamelCase). ex) studentName, woman
메소드 이름은 동사로, lowerCamelCase로 짓는다. ex) study(), eatAll()</p>
<h3 id="생성자">생성자</h3>
<p>생성자를 만들때 필드에 required를 사용하면 인스턴스를 만들때 무조건 그 필드에 대한 값을 지정해줘야 한다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[! [rejected] main -> main (non-fast-forward) 해결방법]]></title>
            <link>https://velog.io/@sue0-si/rejected-main-main-non-fast-forward-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@sue0-si/rejected-main-main-non-fast-forward-%ED%95%B4%EA%B2%B0%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Thu, 30 Nov 2023 09:05:59 GMT</pubDate>
            <description><![CDATA[<h2 id="문제상황">문제상황</h2>
<p>작업물이 로컬 리포지토리 안에 있지 않지만 깃헙에 저장하는 상황에서 push가 되지 않는 경우가 있다. <code>git push origin</code>을 했더니 아래와 같은 메세지가 뜬다.</p>
<pre><code>fatal: The current branch main has no upstream branch.
To push the current branch and set the remost as upstream, use
    git push --set-upstream origin main</code></pre><p>그래서 upstream branch를 설정하기 위해 알려준 대로 명령어를 입력했더니... 아래와 같은 메세지가 뜨면서 push가 되지 않는다.</p>
<pre><code>! [rejected] main -&gt; main (non-fast-forward)</code></pre><p><code>push main</code>을 해도 안되고, 힌트에서 알려준 대로 <code>git pull</code> 을 하면 아래와 같은 메세지가 뜬다. </p>
<p><img src="https://velog.velcdn.com/images/sue0-si/post/03f1f13a-03f4-4c39-bbc6-67803441d4b6/image.png" alt=""></p>
<p>이 문제를 어떻게 해결해야 좋을까? 열심히 검색해서 방법을 찾아냈다...</p>
<h2 id="해결방법">해결방법</h2>
<p>내가 작업하고 있는 디렉토리와 깃헙에 원격으로 저장되는 리포지토리는 서로 관련 기록이 없기 때문에 단순한 git pull은 되지 않는다. 서로 관련 없는 폴더끼리 접근하는 것을 허용한다는 것을 표시해줘야 한다.</p>
<p><code>$ git pull origin main --allow-unrelated-histories</code></p>
<p>이렇게 하고 <code>git push</code> 하면 잘 작동한다. 😂😂😂</p>
]]></description>
        </item>
    </channel>
</rss>