<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>devlp_world</title>
        <link>https://velog.io/</link>
        <description>플러터 개발자 👩🏻‍💻</description>
        <lastBuildDate>Fri, 20 Jun 2025 07:27:13 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>devlp_world</title>
            <url>https://velog.velcdn.com/images/devlp_world/profile/3360eed5-4d9d-42ca-a80e-58f3b20539f6/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. devlp_world. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/devlp_world" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Dart] 선택적 매개변수]]></title>
            <link>https://velog.io/@devlp_world/Dart-%EC%84%A0%ED%83%9D%EC%A0%81-%EB%A7%A4%EA%B0%9C%EB%B3%80%EC%88%98</link>
            <guid>https://velog.io/@devlp_world/Dart-%EC%84%A0%ED%83%9D%EC%A0%81-%EB%A7%A4%EA%B0%9C%EB%B3%80%EC%88%98</guid>
            <pubDate>Fri, 20 Jun 2025 07:27:13 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>명명된 매개변수 <strong>&quot;required&quot;</strong></p>
</blockquote>
<ul>
<li>명명된 매개변수는 특히 매개변수가 많은 함수나 위젯에서 더 많은 유연성을 제공한다
중괄호 {} 안에 정의된 명명된 매개변수는 required 키워드로 표시되지 않는 한 <em>기본적으로 선택적</em> !</li>
<li>함수를 호출할 때 명명된 매개변수는 매개변수 이름을 사용하여 지정되므로, 코드 가독성이 향상된다</li>
</ul>
<blockquote>
<p>선택적 매개변수 초기화</p>
</blockquote>
<pre><code class="language-dart">CompleteButton({
    super.key,
    required this.context,
    required this.onPressed,
    String? text,
    bool? disabled,
  })  
  // 초기화 리스트 이용한 선택적 매개변수 초기값 설정
  : text = text ?? &#39;확인&#39;,
        disabled = disabled ?? false;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Firestore] 관련 DB 기본 개념]]></title>
            <link>https://velog.io/@devlp_world/Firestore-%EA%B4%80%EB%A0%A8-DB-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90</link>
            <guid>https://velog.io/@devlp_world/Firestore-%EA%B4%80%EB%A0%A8-DB-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90</guid>
            <pubDate>Sun, 15 Jun 2025 02:10:29 GMT</pubDate>
            <description><![CDATA[<h4 id="firestore---nosql-형식의-데이터베이스">[Firestore] - NoSQL 형식의 데이터베이스</h4>
<blockquote>
<p><strong>컬렉션(Collection)</strong></p>
</blockquote>
<ul>
<li>일종의 <strong>데이터 모음(폴더)</strong></li>
</ul>
<blockquote>
<p><strong>문서(Document)</strong></p>
</blockquote>
<ul>
<li>컬렉션 안에 들어 있는 <strong>단일 데이터 항목</strong></li>
<li>각각의 문서는 <strong>고유한 ID(uid) 존재</strong></li>
<li>Map 형태의 데이터를 저장</li>
</ul>
<blockquote>
<p><strong>스냅샷(Snapshot)</strong></p>
</blockquote>
<ul>
<li>데이터베이스에서 특정 문서(Document)나 쿼리(Query)의 특정 시점의 데이터의 상태를 <strong>&quot;사진처럼 찍어서&quot;</strong> 저장한 객체</li>
<li><strong>현재 상태 캡처</strong> : 데이터베이스의 특정 시점에 존재하는 데이터를 그대로 가져온다</li>
<li><strong>데이터 읽기</strong> : 문서나 쿼리의 결과를 읽을 때 사용</li>
<li><strong>실시간 추적</strong> :  dart-<code>.snapshots()</code> 메서드를 사용하면 데이터의 변화를 실시간으로 추적 가능</li>
</ul>
<hr>
<blockquote>
<ul>
<li><code>QuerySnapshot&lt;Map&lt;String, dynamic&gt;&gt; QuerySnapshot</code> - 여러 문서 검색 (<strong>collection.where(...)</strong>.get())
→ snapshot.docs.map((doc){}) 로 문서 반복</li>
</ul>
</blockquote>
<ul>
<li><code>DocumentSnapshot&lt;Map&lt;String, dynamic&gt;&gt; DocumentSnapshot</code> - 특정 문서 1개 조회 (<strong>doc(id)</strong>.get())</li>
</ul>
<blockquote>
<p><code>FirebaseFirestore.instance.batch()</code>
여러 개의 쓰기 작업(write operations)을 하나의 트랜잭션처럼 묶어서 실행할 수 있도록 해주는 함수.</p>
</blockquote>
<ul>
<li>즉, 한 번에 여러 문서에 대해 <code>set</code>, <code>update</code>, <code>delete</code> 작업을 수행하고, <strong>모두 성공하거나, 하나라도 실패하면 전부 실패를 보장</strong>하는 역할</li>
<li>이렇게 묶은 작업은 마지막에 <code>batch.commit()</code>을 호출해서 실행한다</li>
<li>예시<pre><code class="language-dart">// Firestore 식물 목록 저장
Future&lt;void&gt; savePlantList(List&lt;PlantModel&gt; plants) async {
final batch = FirebaseFirestore.instance.batch();
  for (final plant in plants) {
      final docRef = _collection.doc(plant.cntntsNo);
      batch.set(docRef, plant.toJson(), SetOptions(merge:     true)); // 병합 저장
    }
await batch.commit();
}</code></pre>
</li>
</ul>
<hr>
<p><strong>🔥 Firestore에서 <code>DocumentReference</code> 와 <code>DocumentSnapshot</code> 차이 완벽 정리</strong></p>
<blockquote>
<p><strong>핵심 차이 요약</strong></p>
</blockquote>
<table>
<thead>
<tr>
<th>구분</th>
<th><code>DocumentReference</code></th>
<th><code>DocumentSnapshot</code></th>
</tr>
</thead>
<tbody><tr>
<td>의미</td>
<td>문서의 <strong>위치(참조)</strong></td>
<td>문서의 <strong>실제 데이터</strong></td>
</tr>
<tr>
<td>포함 내용</td>
<td>문서의 경로, ID 등 메타 정보</td>
<td>문서의 필드 데이터, 존재 여부 등</td>
</tr>
<tr>
<td>주요 메서드</td>
<td><code>set()</code>, <code>update()</code>, <code>delete()</code>, <code>get()</code> 등</td>
<td><code>data()</code>, <code>exists</code>, <code>id</code> 등</td>
</tr>
<tr>
<td>사용 목적</td>
<td>문서에 접근/조작할 때</td>
<td>문서가 있는지 확인하고 데이터를 읽을 때</td>
</tr>
</tbody></table>
<blockquote>
<p><strong>🔗 DocumentReference</strong></p>
</blockquote>
<ul>
<li>Firestore 문서를 <strong>가리키는 참조 객체</strong></li>
<li>예: 문서를 생성하거나, 업데이트, 삭제할 때 사용</li>
<li>예제 코드<pre><code class="language-dart">final docRef = FirebaseFirestore.instance
  .collection(&#39;plants&#39;)
  .doc(&#39;myPlantId&#39;);
await docRef.set({&#39;name&#39;: &#39;로즈마리&#39;}); // 문서 생성 또는 덮어쓰기
await docRef.update({&#39;name&#39;: &#39;바질&#39;}); // 필드만 수정
await docRef.delete(); // 문서 삭제</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Firestore] N-gram 방식으로 쿼리하기]]></title>
            <link>https://velog.io/@devlp_world/Firestore-N-gram-%EB%B0%A9%EC%8B%9D%EC%9C%BC%EB%A1%9C-%EC%BF%BC%EB%A6%AC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@devlp_world/Firestore-N-gram-%EB%B0%A9%EC%8B%9D%EC%9C%BC%EB%A1%9C-%EC%BF%BC%EB%A6%AC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 15 Jun 2025 01:58:05 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://datasciencebeehive.tistory.com/114"><strong>N-gram</strong></a></p>
</blockquote>
<ul>
<li>N-gram은 통계학 기반의 언어 모델 중 하나로, 텍스트나 연설에서 N개의 연속적인 항목(문자, 음절, 단어 등)의 시퀀스를 말합니다. &#39;N&#39;은 숫자를 나타내며, 문자열을 일정한 길이 N으로 끊어서 중복되게 분할하는 방법입니다.</li>
</ul>
<h4 id="현실적인-n-gram-전략">현실적인 N-gram 전략</h4>
<blockquote>
<p>추천: 2~3글자 단위 N-gram <em>(bi-gram/tri-gram)</em></p>
</blockquote>
<ul>
<li>2~3자 조합만 포함,단일 글자는 제외,</li>
<li>검색어가 길든 짧든 일정한 기준으로 매칭 가능</li>
<li>중간 글자 검색도 가능</li>
</ul>
<blockquote>
<p>예: &quot;히포에스테스&quot;
2-gram → [&quot;히포&quot;, &quot;포에&quot;, &quot;에스&quot;, &quot;스테&quot;, &quot;테스&quot;]
3-gram → [&quot;히포에&quot;, &quot;포에스&quot;, &quot;에스테&quot;, &quot;스테스&quot;]</p>
</blockquote>
<blockquote>
<p>N-Gram 생성 코드</p>
</blockquote>
<pre><code class="language-dart">List&lt;String&gt; _generateNGrams(String input) {
  // 1. 공백 제거
  final normalized = input.replaceAll(&#39; &#39;, &#39;&#39;);
  // 2. 중복을 허용하지 않는 컬렉션 Set 생성
  final ngrams = &lt;String&gt;{};
  // 3. 2~3글자 단위로 N-gram 생성
  for (int n = 2; n &lt;= 3; n++) {
    for (int i = 0; i &lt;= normalized.length - n; i++) {
         // 4. n개씩 잘라서 추가
      ngrams.add(normalized.substring(i, i + n)); 
    }
  }
  // 5. Set → List로 변환
  return ngrams.toList();
}</code></pre>
<blockquote>
<p><strong>substring(start, end)</strong></p>
</blockquote>
<ul>
<li>start: 시작 인덱스 (포함)</li>
<li>end: 끝 인덱스 (불포함) ← 중요!</li>
</ul>
<blockquote>
<p><strong>final data = doc.data();</strong></p>
</blockquote>
<ul>
<li>해당 문서 안에 있는 모든 필드를 Map&lt;String, dynamic&gt; 형태로 가져오는 것<pre><code class="language-dart">{
&#39;contentNum&#39;: &#39;18657&#39;,
&#39;plantName&#39;: &#39;얼록자주달개비&#39;,
&#39;photoFileUrl&#39;: &#39;https://...&#39;,
&#39;createdAt&#39;: 1749982130382
}</code></pre>
</li>
</ul>
<blockquote>
<p>전체 코드</p>
</blockquote>
<pre><code class="language-dart">  Future&lt;void&gt; updatePlantsWithNGrams() async {
    DocumentSnapshot? lastDoc;
    while (true) {
      // 1. 데이터 가져오기
      final query = _plantsCollection.limit(10);
      final snapshot =
          lastDoc == null
              ? await query.get()
              : await query.startAfterDocument(lastDoc).get();
      if (snapshot.docs.isEmpty) break;
      // Firestore에서 여러 개의 .set(), .update(), .delete() 작업을 한 번에 처리하는 트랜잭션 비슷한 묶음
      final batch = FirebaseFirestore.instance.batch();
      for (final doc in snapshot.docs) {
        final data = doc.data();
        final plantName = data[&#39;plantName&#39;] ?? &#39;&#39;;
        final ngrams = _generateNGrams(plantName);
        // 2. Firestore에 ngrams 필드 업데이트
        batch.update(doc.reference, {&#39;ngrams&#39;: ngrams});
      }
      // 3. 한꺼번에 commit 완료
      await batch.commit();
      lastDoc = snapshot.docs.last;
    }
    print(&#39;모든 문서에 ngrams 필드 추가 완료&#39;);
  }</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] error : setState() or markNeedsBuild() called during build.]]></title>
            <link>https://velog.io/@devlp_world/Flutter-error-setState-or-markNeedsBuild-called-during-build</link>
            <guid>https://velog.io/@devlp_world/Flutter-error-setState-or-markNeedsBuild-called-during-build</guid>
            <pubDate>Thu, 12 Jun 2025 05:19:01 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>[에러 상황]
회사에서 프로젝트를 진행 하던 중, 리스트 뷰에서 GetXController를 update하는 구문을 호출하였더니, 에러가 발생했다</p>
</blockquote>
<ul>
<li>전체 코드<pre><code class="language-dart">child: ListView.builder(
      itemCount: controller.enterpriseList.length,
      itemBuilder: (context, index) {
         if (controller.hasMore == true &amp;&amp; !controller.isLoading &amp;&amp;
                index == controller.enterpriseList.length - 1) 
             // 이렇게 바로 실행하면 에러
             SdmSearchController.find().search(searchTerm: controller.searchTerm ?? &#39;&#39;);
             return Center(
                 child: CircularProgressIndicator(
                 color: AppTheme.colors.pink,
                 strokeWidth: 2,
               ));
             }</code></pre>
<blockquote>
<ul>
<li>에러 발생한 이유</li>
</ul>
</blockquote>
<ul>
<li>무한 루프나 논리 충돌이 발생할 수 있어서 Flutter가 예외로 막아 놓았기 때문</li>
</ul>
</li>
<li>해결법<ul>
<li><strong>Future.microtask()</strong>로 감싸기</li>
</ul>
</li>
<li>에러가 안 나는 이유
Flutter의 <strong>build phase가 끝난 직후(=마이크로태스크 큐)</strong>에 controller.update()를 실행하게 되기 때문</li>
</ul>
<blockquote>
<p><strong>🔍 개념 정리</strong>
<strong>build() 중에 update() 호출</strong> -&gt; 에러 발생 : 현재 화면을 그리고 있는 중에 다시 그리라고 하면 안 됨
<strong>Future.microtask(() =&gt; controller.update())</strong> : build가 끝난 직후, 마이크로태스크 큐에 실행되니까 안전</p>
</blockquote>
<blockquote>
<p>📌 <strong>Future.microtask()</strong>
 지금 실행중인 동기 작업이 끝나자마자 바로 실행되는 비동기 함수</p>
</blockquote>
<p>즉, build() 함수가 지금 당장 실행되고 있는 중이라면,</p>
<pre><code class="language-dart">controller.update(); // 에러 발생</code></pre>
<p>하지만,</p>
<pre><code class="language-dart">Future.microtask(() =&gt; controller.update()); // 저장해놨다가 바로 실행</code></pre>
<p>Flutter가 build phase를 마치고 난 직후, 즉 다음 이벤트 루프 사이클 전에 update()를 실행하기 때문에 안전하게 UI를 갱신할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] ScrollController 화면 최하단으로 이동하기]]></title>
            <link>https://velog.io/@devlp_world/Flutter-ScrollController-%ED%99%94%EB%A9%B4-%EC%B5%9C%ED%95%98%EB%8B%A8%EC%9C%BC%EB%A1%9C-%EC%9D%B4%EB%8F%99%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@devlp_world/Flutter-ScrollController-%ED%99%94%EB%A9%B4-%EC%B5%9C%ED%95%98%EB%8B%A8%EC%9C%BC%EB%A1%9C-%EC%9D%B4%EB%8F%99%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 11 Jun 2025 08:46:33 GMT</pubDate>
            <description><![CDATA[<h4 id="개발하게-된-배경">개발하게 된 배경</h4>
<blockquote>
<p>회사에서 프로젝트를 진행하던 도중, 어떤 화면에 진입했을때 화면이 다 빌드되고 나서 화면의 최하단으로 스크롤이 이동해야하는 케이스가 생겨 알아보다가 정리하게 되었다</p>
</blockquote>
<hr>
<h4 id="해야하는-작업">해야하는 작업</h4>
<ol>
<li>위젯이 다 빌드 되었는지 확인 - 스크롤 컨트롤러가 위젯에 attach 됐는지 확인하기 위해</li>
<li>스크롤 컨트롤러 position 이동</li>
</ol>
<blockquote>
<p><strong>1. 위젯이 다 빌드 되었는지 확인</strong>
WidgetsBinding.instance.addPostFrameCallback((_) {});</p>
</blockquote>
<ul>
<li>위젯 트리가 다 빌드 되고, 첫 프레임이 렌더링된 후 한번만 실행되는 콜백 함수</li>
</ul>
<blockquote>
<p><strong>2. 스크롤 컨트롤러 position 이동</strong>
scrollController.animateTo(
            scrollController.position.maxScrollExtent,
            duration: const Duration(milliseconds: 1000),
            curve: Curves.easeInOut,
          );</p>
</blockquote>
<ul>
<li>position.maxScrollExtent :  스크롤이 닿을수 있는 전체 영역 중에서 가장 아래(끝)의 위치 값</li>
<li>anmateTo() : 스크롤을 해당되는 위치까지 이동시켜주는 함수</li>
</ul>
<h4 id="전체-코드">전체 코드</h4>
<pre><code class="language-dart">  @override
  void onInit() {
    isLoading = true;
    update();
    Future.wait([
      loadPost(),
      loadCommentList(),
    ]).then((_) {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (moveToComment == true &amp;&amp; scrollController.hasClients) {
          scrollController.animateTo(
            scrollController.position.maxScrollExtent,
            duration: const Duration(milliseconds: 1000),
            curve: Curves.easeInOut,
          );
        }
      });
      isLoading = false;
      update();
    });
    super.onInit();
  }</code></pre>
<hr>
<h4 id="widgetsflutterbinding">WidgetsFlutterBinding</h4>
<blockquote>
<ul>
<li>WidgetsFlutterBinding
runApp() 호출 시 자동으로 생성되어, Flutter 앱의 위젯 바인딩과 렌더링 시스템을 초기화하는 클래스
일반적인 경우에는 명시적으로 사용할 필요가 없지만, main() 함수에서 비동기 작업(예: Firebase 초기화 등)을 수행해야 할 때는,
WidgetsFlutterBinding.ensureInitialized()를 호출해 바인딩을 수동으로 초기화해야 한다.
이 메서드는 바인딩이 아직 없다면 새로 생성하고, 이미 있는 경우 기존 인스턴스를 반환하여 Flutter 엔진이 완전히 초기화되도록 보장한다.</li>
</ul>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] 디스플레이 이름 바꾸는 법]]></title>
            <link>https://velog.io/@devlp_world/Flutter-%EB%94%94%EC%8A%A4%ED%94%8C%EB%A0%88%EC%9D%B4-%EC%9D%B4%EB%A6%84-%EB%B0%94%EA%BE%B8%EB%8A%94-%EB%B2%95</link>
            <guid>https://velog.io/@devlp_world/Flutter-%EB%94%94%EC%8A%A4%ED%94%8C%EB%A0%88%EC%9D%B4-%EC%9D%B4%EB%A6%84-%EB%B0%94%EA%BE%B8%EB%8A%94-%EB%B2%95</guid>
            <pubDate>Tue, 10 Jun 2025 06:10:42 GMT</pubDate>
            <description><![CDATA[<p>[ios]</p>
<blockquote>
<p>info.plist</p>
</blockquote>
<pre><code>    &lt;key&gt;CFBundleName&lt;/key&gt;
    &lt;string&gt;원하는 이름&lt;/string&gt;</code></pre><p>[Android]</p>
<pre><code>    &lt;application
        android:label=&quot;원하는 이름&quot;
        android:name=&quot;${applicationName}&quot;
        android:icon=&quot;@mipmap/ic_launcher&quot;&gt;</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[Firebase] Admin Authentication API Error 목록]]></title>
            <link>https://velog.io/@devlp_world/Firebase-Admin-Authentication-API-Error-%EB%AA%A9%EB%A1%9D</link>
            <guid>https://velog.io/@devlp_world/Firebase-Admin-Authentication-API-Error-%EB%AA%A9%EB%A1%9D</guid>
            <pubDate>Wed, 14 May 2025 02:11:14 GMT</pubDate>
            <description><![CDATA[<ul>
<li>Admin Authentication API Errors<blockquote>
<p><a href="https://firebase.google.com/docs/auth/admin/errors?hl=ko">https://firebase.google.com/docs/auth/admin/errors?hl=ko</a></p>
</blockquote>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Firebase Analytics]]></title>
            <link>https://velog.io/@devlp_world/Firebase-Analytics</link>
            <guid>https://velog.io/@devlp_world/Firebase-Analytics</guid>
            <pubDate>Tue, 13 May 2025 05:01:28 GMT</pubDate>
            <description><![CDATA[<h3 id="firebase-analytics-사용법">Firebase Analytics 사용법</h3>
<blockquote>
<ul>
<li>pub.dev 링크
<a href="https://pub.dev/packages/firebase_analytics">https://pub.dev/packages/firebase_analytics</a></li>
</ul>
</blockquote>
<h4 id="1-기본-설정">1. 기본 설정</h4>
<ol>
<li>Firebase에 내 어플리케이션을 등록한다 (생략)</li>
<li>pubspec.yaml에 firebase_analytics 의존성을 추가한다<blockquote>
<p>firebase_analytics: ^11.4.5</p>
</blockquote>
</li>
</ol>
<hr>
<h4 id="2-추가-기능">2. 추가 기능</h4>
<p>FirebaseAnalytics.instance 와 FirebaseAnalyticsObserver()를 사용하여 이벤트를 추적할수도 있다 (직접 사용하지 않은 기능이라 정리 X)</p>
<pre><code class="language-dart">class App extends StatelessWidget {
  static FirebaseAnalytics analytics = FirebaseAnalytics.instance;
  static FirebaseAnalyticsObserver observer = FirebaseAnalyticsObserver(analytics: analytics);

  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      navigatorObservers: &lt;NavigatorObserver&gt;[observer],
      getPages: [
        GetPage(
          name: &#39;/comments/:id&#39;,
          page: () {
            final id = Get.parameters[&#39;id&#39;]!;
            return CommentDetail(id: id);
          },
        ),
...생략</code></pre>
<hr>
<h4 id="3-debug-view로-확인하기">3. Debug View로 확인하기</h4>
<p>Firebase Console 특성상 analytics 대시보드에 실시간으로 현황이 업데이트 되지 않을 수도 있는데, analytics의 기능이 제대로 실행되고 있는지 디버그뷰를 활용하면 추적할 수 있다
<img src="https://velog.velcdn.com/images/devlp_world/post/6147574c-43ae-4910-8f54-233ef4527c2a/image.png" alt=""></p>
<h4 id="디버그뷰에-기기-등록-하는-법">디버그뷰에 기기 등록 하는 법</h4>
<ol>
<li>Android</li>
</ol>
<ul>
<li>원래는 abd 환경변수 설정을 해줘야 하는데, 하지 않아도 사용할수 있는 방법이 있다<blockquote>
<ol>
<li>터미널에 아래 명령어 입력하면, adb 사용 가능
cd ~/Library/Android/sdk/platform-tools</li>
<li>이후 코드도 동일하게 입력
adb shell setprop debug.firebase.analytics.app 패키지명
<img src="https://velog.velcdn.com/images/devlp_world/post/ff450bdd-68b2-46b2-84c1-0cfa46704e59/image.png" alt=""></li>
</ol>
</blockquote>
</li>
</ul>
<ul>
<li>이후, 안드로이드 기기 디버그 시작 시 디버그뷰에서 확인할 수 있다
<img src="https://velog.velcdn.com/images/devlp_world/post/9b457ffe-6388-46b2-bff9-640f9a069306/image.png" alt=""></li>
</ul>
<ol start="2">
<li>iOS</li>
</ol>
<ul>
<li>Xcode &gt; Runner &gt; Edit Scheme...</li>
<li>Arguments Passed On Launch + &gt; <em>-FIRDebugEnabled</em> 입력
<img src="https://velog.velcdn.com/images/devlp_world/post/b725af50-5517-409f-b60b-fbca05619250/image.png" alt=""></li>
<li>이후, 안드로이드와 동일하게 기기 디버그 시작 시 디버그뷰에서 확인할 수 있다</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js 환경설정]]></title>
            <link>https://velog.io/@devlp_world/Next.js-%EC%84%A4%EC%B9%98</link>
            <guid>https://velog.io/@devlp_world/Next.js-%EC%84%A4%EC%B9%98</guid>
            <pubDate>Fri, 09 May 2025 00:40:25 GMT</pubDate>
            <description><![CDATA[<p><a href="https://nextjs.org/docs/app/getting-started/installation">NEXT.JS 설치 관련 문서</a></p>
<ul>
<li>npm 설치 <blockquote>
<p>npm install next@latest react@latest react-dom@latest</p>
</blockquote>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] ios/Android 카메라 및 갤러리 접근권한 요청]]></title>
            <link>https://velog.io/@devlp_world/Flutter-iosAndroid-%EC%B9%B4%EB%A9%94%EB%9D%BC-%EB%B0%8F-%EA%B0%A4%EB%9F%AC%EB%A6%AC-%EA%B6%8C%ED%95%9C-%EC%9A%94%EC%B2%AD</link>
            <guid>https://velog.io/@devlp_world/Flutter-iosAndroid-%EC%B9%B4%EB%A9%94%EB%9D%BC-%EB%B0%8F-%EA%B0%A4%EB%9F%AC%EB%A6%AC-%EA%B6%8C%ED%95%9C-%EC%9A%94%EC%B2%AD</guid>
            <pubDate>Mon, 05 May 2025 15:04:49 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>Flutter의 패키지 중 <strong>image_picker</strong> 라는 패키지를 사용하여 카메라와 갤러리에 접근해서 사진을 불러오는 기능을 구현하려고 한다
먼저, iOS와 Android에서 카메라 및 갤러리에 접근하려면 별도의 <strong>접근 권한</strong>이 필요하다</p>
</blockquote>
<h3 id="ios">iOS</h3>
<h4 id="접근-권한-추가">접근 권한 추가</h4>
<ul>
<li><p>ios &gt; Runner &gt; Info.plist 코드 추가</p>
<pre><code class="language-xml">  &lt;key&gt;NSCameraUsageDescription&lt;/key&gt;
  &lt;string&gt;Access to the camera is required for image search.&lt;/string&gt;
  &lt;key&gt;NSPhotoLibraryUsageDescription&lt;/key&gt;
  &lt;string&gt;Access to the photo library is required for image search.&lt;/string&gt;</code></pre>
</li>
<li><p>ios &gt; Podfile 코드 추가</p>
<pre><code class="language-swift">post_install do |installer|
installer.pods_project.targets.each do |target|
  flutter_additional_ios_build_settings(target)
end

target.build_configurations.each do |config|
  config.build_settings[&#39;GCC_PREPROCESSOR_DEFINITIONS&#39;] ||= [
    &#39;$(inherited)&#39;,
    &#39;PERMISSION_PHOTOS=1&#39;, # 사진 라이브러리 권한 플래그 설정
    &#39;PERMISSION_CAMERA=1&#39;, # 카메라 권한 플래그 설정 
  ]
end
end</code></pre>
</li>
</ul>
<hr>
<h3 id="android">Android</h3>
<h4 id="접근-권한-추가-1">접근 권한 추가</h4>
<ul>
<li>Android 13 이상부터 더이상 카메라 갤러리 권한 요청이 필수가 아니게 되었다</li>
<li>android &gt; app &gt; src &gt; main &gt; AndroidManifest.xml 코드 추가<pre><code class="language-xml">&lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;&gt;
  &lt;!-- 인터넷 --&gt;
  &lt;uses-permission android:name=&quot;android.permission.INTERNET&quot;/&gt;
  &lt;!-- Android 12 이하 --&gt;
  &lt;uses-permission android:name=&quot;android.permission.READ_EXTERNAL_STORAGE&quot; /&gt;
</code></pre>
</li>
</ul>
<p>```</p>
<hr>
<h4 id="참고">참고</h4>
<ul>
<li><a href="https://github.com/Baseflow/flutter-permission-handler/blob/main/permission_handler_apple/ios/Classes/PermissionHandlerEnums.h">permission_handler Enum 정리</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] ValueChanged<T>]]></title>
            <link>https://velog.io/@devlp_world/Flutter-ValueChangedT</link>
            <guid>https://velog.io/@devlp_world/Flutter-ValueChangedT</guid>
            <pubDate>Wed, 23 Apr 2025 06:29:48 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><code>ValueChanged&lt;T&gt;? functionName;</code></p>
</blockquote>
<ul>
<li>하나의 인자(T 타입)를 받고 void 값을 반환하는 콜백 타입으로,
Flutter에서 미리 정의된 함수 타입(Function typedef) 중 하나.
위젯에서 상태 변경 시 부모에게 알려줄 때 자주 쓰임</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] timeago]]></title>
            <link>https://velog.io/@devlp_world/Flutter-timeago</link>
            <guid>https://velog.io/@devlp_world/Flutter-timeago</guid>
            <pubDate>Thu, 10 Apr 2025 01:09:05 GMT</pubDate>
            <description><![CDATA[<p>플러터로 위젯을 빌드할때, 현재시간과 비교해서 몇분전 혹은 몇시간전/며칠 전 인지에 대한 시간 정보를 보여줘야 할 때 사용할 수 있는 유용한 패키지</p>
<blockquote>
<p><a href="https://pub.dev/packages/timeago">https://pub.dev/packages/timeago</a></p>
</blockquote>
<h3 id="사용-방법">사용 방법</h3>
<h4 id="1-의존성-추가">1. 의존성 추가</h4>
<p>- pubspec.yaml에 아래 코드 추가</p>
<pre><code class="language-yaml">timeago: ^3.7.0</code></pre>
<h4 id="2-예시">2. 예시</h4>
<ul>
<li><p>import
<code>import &#39;package:timeago/timeago.dart&#39; as timeago;</code></p>
</li>
<li><p>코드
```dart
static Widget requestInfo(PostModel post) {</p>
<pre><code>// locale 설정</code></pre><p>  timeago.setLocaleMessages(&#39;ko&#39;, timeago.KoMessages());
  DateTime createdDate = DateTime.fromMillisecondsSinceEpoch(post.createdAt);
  return Row(</p>
<pre><code>children: [
  const SizedBox(width: 5),
  Container(
    width: 4,
    height: 4,
    decoration: BoxDecoration(
      color: const Color(0xFFFF0000),
      borderRadius: BorderRadius.circular(100),
    ),
  ),
  const SizedBox(width: 12),
  Expanded(
    child: Text(
      post.title,
      maxLines: 1,
      overflow: TextOverflow.ellipsis,
    ),
  ),
  const SizedBox(width: 25),
  Text(timeago.format(createdDate, locale: &#39;ko&#39;)),
  const SizedBox(width: 5),
],</code></pre><p>  );
}```</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] futureBuilder]]></title>
            <link>https://velog.io/@devlp_world/flutter-futureBuilder</link>
            <guid>https://velog.io/@devlp_world/flutter-futureBuilder</guid>
            <pubDate>Thu, 10 Apr 2025 01:04:50 GMT</pubDate>
            <description><![CDATA[<h4 id="futurebuilder">FutureBuilder</h4>
<blockquote>
<p>비동기 작업(Future)이 끝날 때까지 기다렸다가, 결과에 따라 UI를 바꿔주는 Flutter 위젯</p>
</blockquote>
<ul>
<li>주로, 데이터를 파일에서 읽거나, 인터넷에서 받아올 때 사용</li>
</ul>
<h4 id="구조">구조</h4>
<pre><code class="language-dart">FutureBuilder&lt;T&gt;(
  future: someFuture,
  builder: (context, snapshot) {
    // snapshot 안에 결과, 상태, 오류 정보가 들어 있음
  },
)</code></pre>
<h4 id="snapshot">snapshot</h4>
<blockquote>
<p>비동기 처리 중 생기는 <strong>데이터의 상태와 결과를 담고 있는 객체</strong>
<code>FutureBuilder</code>나 <code>StreamBuilder</code>에서 <code>builder</code> 함수의 인자로 전달</p>
</blockquote>
<h4 id="🔍-snapshot-속성-정리">🔍 snapshot 속성 정리</h4>
<table>
<thead>
<tr>
<th>속성</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>snapshot.hasData</code></td>
<td><code>true</code>면 데이터가 성공적으로 도착한 상태 즉, <code>snapshot.data</code>를 안전하게 사용할 수 있다</td>
</tr>
<tr>
<td><code>snapshot.data</code></td>
<td>비동기 작업의 결과로 도착한 <strong>실제 데이터</strong></td>
</tr>
<tr>
<td><code>snapshot.connectionState</code></td>
<td>현재 비동기 작업의 상태 <code>ConnectionState.waiting</code>, <code>done</code> 등을 통해 로딩 중인지 완료됐는지를 알 수 있다</td>
</tr>
<tr>
<td><code>snapshot.hasError</code></td>
<td><code>true</code>면 비동기 작업 중 <strong>에러가 발생</strong>한 상태</td>
</tr>
<tr>
<td><code>snapshot.error</code></td>
<td>발생한 에러에 대한 정보를 담고 있다. 로그 출력이나 사용자에게 에러 메시지를 보여줄 때 사용</td>
</tr>
</tbody></table>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] flutter_oss_licenses]]></title>
            <link>https://velog.io/@devlp_world/flutterosslicenses</link>
            <guid>https://velog.io/@devlp_world/flutterosslicenses</guid>
            <pubDate>Wed, 09 Apr 2025 00:58:48 GMT</pubDate>
            <description><![CDATA[<p>플러터에서 내가 사용한 오픈소스 라이센스를 손쉽게 보여주도록 지원해주는 패키지가 있다</p>
<blockquote>
<p><a href="%3Ehttps://pub.dev/packages/flutter_oss_licenses">flutter_oss_licenses</a></p>
</blockquote>
<hr>
<h3 id="사용-방법">사용 방법</h3>
<h4 id="1-의존성-추가">1. 의존성 추가</h4>
<p>- pubspec.yaml에 아래 코드 추가</p>
<pre><code class="language-yaml">flutter_oss_licenses: ^3.0.4</code></pre>
<h4 id="2-json-파일-생성">2. json 파일 생성</h4>
<ul>
<li>flutter_oss_licenses 3.0.0 이후부터는 <strong>.json 출력만</strong> 지원하고,</li>
<li><em>--text*</em>나** --markdown** 옵션은 <strong>더 이상 지원해주지 않음</strong><blockquote>
<p>run flutter_oss_licenses:generate -o assets/json/licenses.json --json</p>
</blockquote>
</li>
<li>내가 지정한 폴더 경로에 오픈소스 json 파일이 생성된다</li>
</ul>
<h4 id="3-예시">3. 예시</h4>
<p><img src="https://velog.velcdn.com/images/devlp_world/post/93a0082b-3c2b-4081-af0e-9ec3aa4962ba/image.png" alt=""></p>
<hr>
<h3 id="json-파일-활용해서-자유롭게-화면-구현하기">json 파일 활용해서 자유롭게 화면 구현하기</h3>
<h4 id="1-pubspecyaml-등록하기">1. pubspec.yaml 등록하기</h4>
<pre><code class="language-yaml">flutter:
  uses-material-design: true
  assets:
    - assets/
    - assets/json/licenses.json</code></pre>
<h4 id="2-화면-구현-예시">2. 화면 구현 예시</h4>
<pre><code class="language-dart">import &#39;dart:convert&#39;;
import &#39;package:flutter/material.dart&#39;;
import &#39;package:flutter/services.dart&#39; show rootBundle;
import &#39;package:marrying_flutter/app_theme.dart&#39;;

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

  Future&lt;List&lt;Map&lt;String, dynamic&gt;&gt;&gt; loadLicenses() async {
    final jsonString = await rootBundle.loadString(&#39;assets/json/licenses.json&#39;);
    final List&lt;dynamic&gt; jsonData = json.decode(jsonString);
    return jsonData.map((e) =&gt; e as Map&lt;String, dynamic&gt;).toList();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text(&#39;오픈소스 라이센스&#39;)),
      body: FutureBuilder&lt;List&lt;Map&lt;String, dynamic&gt;&gt;&gt;(
        future: loadLicenses(),
        builder: (context, snapshot) {
          if (!snapshot.hasData) {
            return Center(
                child: CircularProgressIndicator(
              color: AppThemeColors().pink,
              strokeWidth: 2,
            ));
          }
          final licenses = snapshot.data!;

          return ListView.separated(
            padding: const EdgeInsets.all(16),
            itemCount: licenses.length,
            separatorBuilder: (_, __) =&gt; Divider(
              color: AppThemeColors().divider,
            ),
            itemBuilder: (context, index) {
              final license = licenses[index];
              final name = license[&#39;name&#39;] ?? &#39;Unknown&#39;;
              final homepage = license[&#39;homepage&#39;] ?? license[&#39;repository&#39;];

              return ListTile(
                title: Text(
                  name,
                  style: TextStyle(fontSize: 15, fontWeight: AppThemeFontWeights().medium),
                ),
                subtitle: homepage != null
                    ? Text(
                        homepage,
                        style: TextStyle(color: AppThemeColors().grey73),
                      )
                    : null,
              );
            },
          );
        },
      ),
    );
  }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[[FirebaseCore]] 11.6.0 - [FirebaseCore][I-COR000005] No app has been configured yet.]]></title>
            <link>https://velog.io/@devlp_world/FirebaseCore-11.6.0-FirebaseCoreI-COR000005-No-app-has-been-configured-yet</link>
            <guid>https://velog.io/@devlp_world/FirebaseCore-11.6.0-FirebaseCoreI-COR000005-No-app-has-been-configured-yet</guid>
            <pubDate>Fri, 04 Apr 2025 01:45:05 GMT</pubDate>
            <description><![CDATA[<h3 id="문제-상황">문제 상황</h3>
<ul>
<li>[firebase Core] 아직 앱이 구성되지 않았다는 디버그 코드가 출력됨<blockquote>
<p>[[FirebaseCore]] 11.6.0 - [FirebaseCore][I-COR000005] No app has been configured yet.</p>
</blockquote>
</li>
</ul>
<h3 id="해결-방법">해결 방법</h3>
<ul>
<li><p>ios &gt; AppDelegate.swift 코드 추가</p>
<pre><code class="language-dart">import Firebase

@main
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -&gt; Bool {
    **FirebaseApp.configure()**
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
  ...</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Swift Compiler Error (Xcode): No such module 'FirebaseMessaging']]></title>
            <link>https://velog.io/@devlp_world/Swift-Compiler-Error-Xcode-No-such-module-FirebaseMessaging</link>
            <guid>https://velog.io/@devlp_world/Swift-Compiler-Error-Xcode-No-such-module-FirebaseMessaging</guid>
            <pubDate>Wed, 02 Apr 2025 01:54:37 GMT</pubDate>
            <description><![CDATA[<h3 id="문제-상황">문제 상황</h3>
<ul>
<li>xcode 버전 업데이트 이후, 갑자기 FirebaseMessaging 모듈이 없다고 빌드가 되지 않는 에러 발생
<img src="https://velog.velcdn.com/images/devlp_world/post/2d23562c-30ae-4775-b503-084c9edf00f9/image.png" alt=""><blockquote>
<p>Failed to build iOS app</p>
</blockquote>
</li>
<li><ul>
<li>Swift Compiler Error (Xcode): No such module &#39;FirebaseMessaging&#39;**
/Users/seeun/projects/marrying-flutter/ios/ImageNotification/NotificationService.swift:8:7
Encountered error while building for device.</li>
</ul>
</li>
</ul>
<h3 id="해결-방법">해결 방법</h3>
<ul>
<li>ios &gt; podfile 코드 추가<pre><code class="language-swift">  target &#39;ImageNotification&#39; do
    use_frameworks!
    pod &#39;Firebase/Messaging&#39;
  end</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] Firebase 로그인 : Naver]]></title>
            <link>https://velog.io/@devlp_world/Flutter-Firebase-%EB%A1%9C%EA%B7%B8%EC%9D%B8-Naver</link>
            <guid>https://velog.io/@devlp_world/Flutter-Firebase-%EB%A1%9C%EA%B7%B8%EC%9D%B8-Naver</guid>
            <pubDate>Sun, 23 Mar 2025 11:13:19 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>네이버 로그인 연동은 Firebase 공식 지원 패키지가 아닌 <strong>서드파티 패키지</strong>이므로, 직접 구축한 서버에서 <strong>Custom Token</strong>을 <strong>발급</strong> 받을 수 있어야 합니다.</p>
</blockquote>
<h3 id="사용한-플러그인">사용한 플러그인</h3>
<blockquote>
<p>flutter_naver_login : <a href="https://pub.dev/packages/flutter_naver_login">https://pub.dev/packages/flutter_naver_login</a></p>
</blockquote>
<h3 id="android-설정">Android 설정</h3>
<h4 id="1-app--src--main--res--stringsxml-파일-추가">1. app &gt; src &gt; main &gt; res &gt; strings.xml 파일 추가</h4>
<pre><code class="language-xml">    &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
    &lt;resources&gt;
        &lt;string name=&quot;client_id&quot;&gt;client_id&lt;/string&gt;
        &lt;string name=&quot;client_secret&quot;&gt;client_secret&lt;/string&gt;
        &lt;string name=&quot;client_name&quot;&gt;client_name&lt;/string&gt;
    &lt;/resources&gt;</code></pre>
<ul>
<li>본인이 설정한 <code>naverConsumerKey</code> <code>naverConsumerSecret</code>
<code>naverServiceAppName</code> <code>naverServiceAppName</code> <code>naverServiceAppUrlScheme</code>
을 <code>&lt;string&gt;&lt;/string&gt;</code> 사이에 넣기</li>
</ul>
<h4 id="2-androidmenifestxml-수정">2. AndroidMenifest.xml 수정</h4>
<pre><code class="language-xml">    &lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;&gt;
        &lt;uses-permission android:name=&quot;android.permission.INTERNET&quot;/&gt;
        &lt;application
            android:label=&quot;${라벨이름}&quot;
            android:name=&quot;${applicationName}&quot;
            android:icon=&quot;@mipmap/ic_launcher&quot;&gt;
            &lt;!--meta-data 추가--&gt;
           &lt;meta-data
               android:name=&quot;com.naver.sdk.clientId&quot;
               android:value=&quot;@string/client_id&quot; /&gt;
            &lt;meta-data
               android:name=&quot;com.naver.sdk.clientSecret&quot;
               android:value=&quot;@string/client_secret&quot; /&gt;
            &lt;meta-data
               android:name=&quot;com.naver.sdk.clientName&quot;
               android:value=&quot;@string/client_name&quot; /&gt;
            &lt;activity
                      ...</code></pre>
<h4 id="3-문제-상황">3. 문제 상황</h4>
<p>[이슈 상황] kotiln과 java 컴파일러의 버전 호환성 에러 발생
[해결 방법]</p>
<ul>
<li>settings.gradle 수정          <pre><code class="language-kotlin">plugins {
   id &quot;dev.flutter.flutter-plugin-loader&quot; version &quot;1.0.0&quot;
   id &quot;com.android.application&quot; version &quot;8.1.0&quot; apply false
   // START: FlutterFire Configuration
  id &quot;com.google.gms.google-services&quot; version &quot;4.3.15&quot; apply false
  // END: FlutterFire Configuration
  // id &quot;org.jetbrains.kotlin.android&quot; version 
  **&quot;1.8.22&quot;** apply false
  // flutter_naver_login ^2.0.0. 호환시키기 위해 버전 변경
  id &quot;org.jetbrains.kotlin.android&quot; version **&quot;2.0.21&quot;** apply false
 }</code></pre>
</li>
</ul>
<hr>
<h3 id="ios-설정">iOS 설정</h3>
<h4 id="1-infoplist-수정">1. info.plist 수정</h4>
<pre><code class="language-xml">  &lt;!--array에 추가--&gt;
  &lt;dict&gt;
          &lt;key&gt;CFBundleTypeRole&lt;/key&gt;
          &lt;string&gt;Editor&lt;/string&gt;
          &lt;key&gt;CFBundleURLSchemes&lt;/key&gt;
          &lt;array&gt;
              &lt;string&gt;com.temp.test&lt;/string&gt;
          &lt;/array&gt;
      &lt;/dict&gt;
     ...
    &lt;!--가장 하단에 추가--&gt;
    &lt;key&gt;naverConsumerKey&lt;/key&gt;
    &lt;string&gt;$ConsumerKey&lt;/string&gt;
    &lt;key&gt;naverConsumerSecret&lt;/key&gt;
    &lt;string&gt;$ConsumerSecret&lt;/string&gt;
    &lt;key&gt;naverServiceAppName&lt;/key&gt;
    &lt;string&gt;$AppName&lt;/string&gt;
    &lt;key&gt;naverServiceAppUrlScheme&lt;/key&gt;
    &lt;string&gt;$AppUrlScheme&lt;/string&gt;</code></pre>
<ul>
<li>본인이 설정한 <code>naverConsumerKey</code> <code>naverConsumerSecret</code>
<code>naverServiceAppName</code> <code>naverServiceAppName</code> <code>naverServiceAppUrlScheme</code>
을 <code>&lt;string&gt;&lt;/string&gt;</code> 사이에 넣기<h4 id="2-appdelegateswift-수정">2. AppDelegate.swift 수정</h4>
</li>
</ul>
<pre><code class="language-swift">    ...
    import NaverThirdPartyLogin

      // 가장 하단에 추가
      override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -&gt; Bool {
          var applicationResult = false
          if (!applicationResult) {
             applicationResult = NaverThirdPartyLoginConnection.getSharedInstance().application(app, open: url, options: options)
          }
          // if you use other application url process, please add code here.

          if (!applicationResult) {
             applicationResult = super.application(app, open: url, options: options)
          }
          return applicationResult
      }</code></pre>
<hr>
<h3 id="코드-구현-예시">코드 구현 예시</h3>
<ul>
<li><p>pubspec.yaml 수정</p>
<blockquote>
<p>flutter_naver_login: ^2.0.0 # 플러그인 추가</p>
</blockquote>
</li>
<li><p>signinWithNaver()</p>
<pre><code class="language-dart">signinWithNaver() {
  IndicatorClient.instance.show();
  return FirebaseClient.auth.signinWithNaver().then((_) {
    return NetworkClient.auth.create(&#39;naver&#39;).then((_) {
      IndicatorClient.instance.hide();
      BaseNavigator.reset(const SplashRoute());
    });
  }).catchError((e) {
    // print(e);
    IndicatorClient.instance.hide();
    if (e == &#39;error&#39;) {
      BaseDialog.showToast(&#39;로그인이 취소되었습니다&#39;);
      return;
    }
    BaseDialog.showToast(&#39;오류가 발생했습니다&#39;);
  });
}</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Android] 릴리즈 키 생성 및 해시키 추출]]></title>
            <link>https://velog.io/@devlp_world/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EB%A6%B4%EB%A6%AC%EC%A6%88-%ED%82%A4-%EC%83%9D%EC%84%B1-%EB%B0%8F-%ED%95%B4%EC%8B%9C%ED%82%A4-%EC%B6%94%EC%B6%9C</link>
            <guid>https://velog.io/@devlp_world/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EB%A6%B4%EB%A6%AC%EC%A6%88-%ED%82%A4-%EC%83%9D%EC%84%B1-%EB%B0%8F-%ED%95%B4%EC%8B%9C%ED%82%A4-%EC%B6%94%EC%B6%9C</guid>
            <pubDate>Sun, 23 Mar 2025 07:07:52 GMT</pubDate>
            <description><![CDATA[<h3 id="1-업로드-키스토어-생성">1. 업로드 키스토어 생성</h3>
<h4 id="1-명령어">1. 명령어</h4>
<ul>
<li>macOS 또는 Linux <blockquote>
<p>keytool -genkey -v -keystore ~/upload-keystore.jks -keyalg RSA \</p>
<pre><code>  -keysize 2048 -validity 10000 -alias upload</code></pre></blockquote>
</li>
</ul>
<ul>
<li>Windows &gt; PowerShell<blockquote>
<p>keytool -genkey -v -keystore $env:USERPROFILE\upload-keystore.jks <code>-storetype JKS -keyalg RSA -keysize 2048 -validity 10000</code>        -alias upload</p>
</blockquote>
</li>
</ul>
<h4 id="2-인증-절차">2. 인증 절차</h4>
<ul>
<li><p>모든 항목을 입력하지 않아도 됨</p>
</li>
<li><p>두 자리 국가 코드는 대한민국이므로 &quot;82&quot;</p>
</li>
<li><p>upload에 대한 키 비밀번호 입력</p>
<ul>
<li>Enter를 누르시면 위에서 입력한 키 저장소 비밀번호/새 비밀번호 항목에 입력한 비밀번호와 동일하게 설정</li>
<li>비밀번호의 경우, 추후 앱 서명시 필요. 잊어버리지 않도록 주의 → company명 (pillgood) 으로 설정</li>
</ul>
</li>
<li><p>.jks 파일 → android 하위 디렉토리 &gt; import</p>
<ul>
<li><del>import 하는건지 자동생성 되는건지 다음 프로젝트 때 확인 필요</del>
<img src="https://velog.velcdn.com/images/devlp_world/post/fa9449ab-7517-49f2-9a3e-e5a6dbd26401/image.png" alt=""></li>
</ul>
</li>
</ul>
<hr>
<h3 id="2-앱에서-키스토어-참조">2. 앱에서 키스토어 참조</h3>
<h4 id="1-keystoreproperties-파일을-로드할-객체-설정">1. keystoreProperties 파일을 로드할 객체 설정</h4>
<ul>
<li>android &gt; key.properties 파일 추가<br><img src="https://velog.velcdn.com/images/devlp_world/post/ba503b4d-6cad-4603-830e-e91363c712a5/image.png" alt=""><blockquote>
<p>storePassword=...
keyPassword=...
keyAlias=...
storeFile= ...-keystore.jks</p>
</blockquote>
</li>
</ul>
<hr>
<h3 id="3-gradle에서-로그인-구성">3. Gradle에서 로그인 구성</h3>
<ul>
<li>서명 구성 추가<ul>
<li>android &gt; app &gt; build.gradle 수정<pre><code class="language-dart"># 속성 블록 전에 키스토어 속성 파일을 정의하고 로드
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file(&#39;key.properties&#39;)
if (keystorePropertiesFile.exists()) {
   keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
...
# buildTypes속성 블록 내부의 속성 블록 앞
signingConfigs {
    release {
        keyAlias keystoreProperties[&#39;keyAlias&#39;]
        keyPassword keystoreProperties[&#39;keyPassword&#39;]
        storeFile keystoreProperties[&#39;storeFile&#39;] ? file(keystoreProperties[&#39;storeFile&#39;]) : null
        storePassword keystoreProperties[&#39;storePassword&#39;]
    }
}</code></pre>
</li>
</ul>
</li>
</ul>
<hr>
<h3 id="4-앱-매니페스트-검토">4. 앱 매니페스트 검토</h3>
<ul>
<li>앱 매니페스트 검토</li>
</ul>
<h3 id="5-gradle-빌드-구성-검토-및-변경">5. Gradle 빌드 구성 검토 및 변경</h3>
<ul>
<li>Gradle 빌드 구성 검토 및 변경</li>
</ul>
<hr>
<h3 id="6-buildgradle-속성-조정">6. build.gradle 속성 조정</h3>
<ul>
<li><p>속성</p>
<table>
<thead>
<tr>
<th>재산</th>
<th>목적</th>
<th>기본값</th>
</tr>
</thead>
<tbody><tr>
<td><code>compileSdk</code></td>
<td>앱이 컴파일되는 Android API 수준입니다. 이는 사용 가능한 가장 높은 버전이어야 합니다. 이 속성을 로 설정하면 앱이 에 특정한 API를 사용하지 않는 한 <code>31</code>API 또는 이전 버전을 실행하는 기기에서 앱을 실행합니다 .<code>3031</code></td>
<td></td>
</tr>
<tr>
<td><code>defaultConfig</code></td>
<td></td>
<td></td>
</tr>
<tr>
<td><code>.applicationId</code></td>
<td>앱을 식별하는 최종 고유 <a href="https://developer.android.com/studio/build/application-id">애플리케이션 ID입니다 .</a></td>
<td></td>
</tr>
<tr>
<td><code>.minSdk</code></td>
<td>앱이 실행되도록 설계한 최소 <a href="https://developer.android.com/studio/publish/versioning#minsdk">Android API 레벨 입니다.</a></td>
<td><code>flutter.minSdkVersion</code></td>
</tr>
<tr>
<td><code>.targetSdk</code></td>
<td>앱 실행을 테스트한 Android API 레벨입니다. 앱은 이 레벨까지의 모든 Android API 레벨에서 실행되어야 합니다.</td>
<td><code>flutter.targetSdkVersion</code></td>
</tr>
<tr>
<td><code>.versionCode</code></td>
<td><a href="https://developer.android.com/studio/publish/versioning">내부 버전 번호를</a> 설정하는 양의 정수 . 이 번호는 어느 버전이 다른 버전보다 최신인지만 결정합니다. 숫자가 클수록 최신 버전을 나타냅니다. 앱 사용자는 이 값을 절대 보지 못합니다.</td>
<td></td>
</tr>
<tr>
<td><code>.versionName</code></td>
<td>앱이 버전 번호로 표시하는 문자열입니다. 이 속성을 원시 문자열 또는 문자열 리소스에 대한 참조로 설정합니다.</td>
<td></td>
</tr>
<tr>
<td><code>.buildToolsVersion</code></td>
<td>Gradle 플러그인은 프로젝트에서 사용하는 Android 빌드 도구의 기본 버전을 지정합니다. 빌드 도구의 다른 버전을 지정하려면 이 값을 변경합니다.</td>
<td></td>
</tr>
</tbody></table>
</li>
</ul>
<hr>
<h3 id="flutter-proejct-재구성">flutter proejct 재구성</h3>
<ul>
<li>다음과 같이 변경하면, 캐시된 빌드로 인해 서명 프로레스에 영향을 미칠 수도 있으므로,
다음 명령문을 통해, <code>flutter proejct</code> 재구성하기
- <code>flutter clean</code> <code>flutter pub get</code></li>
</ul>
<hr>
<h3 id="해시-키-추출">해시 키 추출</h3>
<ul>
<li><p>release key</p>
<blockquote>
<p>keytool -list -v -alias {aliasName} -keystore {keystoreName}.jks</p>
</blockquote>
</li>
<li><p>Terminal &gt; <code>aliasName</code> 과 <code>keystoreName</code>에 내가 설정한 대로 변경 후,
코드 입력하면 SHA-1 키가 추출됨</p>
</li>
<li><p>SHA-1 키 변환</p>
<blockquote>
<p>$ echo {SHA-1} | xxd -r -p | openssl base64</p>
</blockquote>
</li>
<li><p>추출된 해시 키
- Firebase 및 kakao developer 해시 키 추가 필수 !</p>
</li>
<li><p>Google play console</p>
<ul>
<li>goole play console 안드로이드 릴리즈 해시 키 업로드
- google play console 서명 키 Firebase 및 kakao developer에 추가하기</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Apple] iOS 앱 빌드 및 릴리즈]]></title>
            <link>https://velog.io/@devlp_world/Apple-Appstore-Connet-%EC%95%B1-%EB%B9%8C%EB%93%9C-%EB%B0%8F-%EB%A6%B4%EB%A6%AC%EC%A6%88-7h8z8o7t</link>
            <guid>https://velog.io/@devlp_world/Apple-Appstore-Connet-%EC%95%B1-%EB%B9%8C%EB%93%9C-%EB%B0%8F-%EB%A6%B4%EB%A6%AC%EC%A6%88-7h8z8o7t</guid>
            <pubDate>Sun, 23 Mar 2025 06:36:08 GMT</pubDate>
            <description><![CDATA[<h3 id="1-번들-id-등록">1. 번들 ID 등록</h3>
<h4 id="1-xcode-에서-bundle-id-확인">1. XCode 에서 Bundle ID 확인</h4>
<p><img src="https://velog.velcdn.com/images/devlp_world/post/5e6e6b17-07f6-47c6-8d2d-678b2b272b08/image.png" alt=""></p>
<h4 id="2-apple-developer에서-app-id-등록">2. Apple developer에서 App ID 등록</h4>
<ul>
<li>Certificates, Identifiers &amp; Profiles : Identifiers &gt; + 버튼 클릭<pre><code>  - Desription/BundleID 입력</code></pre>  <img src="https://velog.velcdn.com/images/devlp_world/post/f9f2732e-944c-4023-a185-b8e31ac15a13/image.png" alt=""></li>
</ul>
<hr>
<h3 id="2-app-store-connect-앱-등록">2. App Store Connect 앱 등록</h3>
<ul>
<li>App Store Connet &gt; 앱 &gt; + 버튼 클릭<blockquote>
<p>-이름 : 앱 이름
-기본 언어 : 기본 언어 선택
-번들 ID : 앱 번들 ID 입력
-SKU : 사용자에게 표시되지 않는 앱의 고유한 ID
  <img src="https://velog.velcdn.com/images/devlp_world/post/1becfc1c-4d42-4f54-bf84-5b648837b6ad/image.png" alt=""></p>
</blockquote>
</li>
</ul>
<hr>
<h3 id="3-xcode-설정-검토">3. Xcode 설정 검토</h3>
<ul>
<li><a href="https://docs.flutter.dev/deployment/ios#review-xcode-project-settings">https://docs.flutter.dev/deployment/ios#review-xcode-project-settings</a></li>
</ul>
<hr>
<h3 id="4-앱-배포-버전-업데이트">4. 앱 배포 버전 업데이트</h3>
<ul>
<li><code>Deployment Target</code>Xcode 프로젝트에서 변경한 경우, <code>ios/Flutter/AppframeworkInfo.plist</code>Flutter 앱에서 열고, <code>MinimumOSVersion</code>값을 일치하도록 업데이트</li>
</ul>
<hr>
<h3 id="5-앱-아이콘-추가">5. 앱 아이콘 추가</h3>
<ul>
<li><a href="https://developer.apple.com/design/human-interface-guidelines/app-icons/">https://developer.apple.com/design/human-interface-guidelines/app-icons/</a></li>
</ul>
<hr>
<h3 id="6-출시-이미지-추가">6. 출시 이미지 추가</h3>
<ul>
<li>Xcode 프로젝트 탐색기에서 폴더 <code>Assets.xcassetsRunner</code>  선택</li>
<li>&#39;placeholder launch image&#39; 를 원하는 이미지로 변경</li>
<li>앱 hot restart &gt; 새로운 시작 이미지를 확인</li>
</ul>
<hr>
<h3 id="7-앱의-빌드-및-버전-번호-업데이트">7. 앱의 빌드 및 버전 번호 업데이트</h3>
<ul>
<li>앱의 기본 버전 번호는 <code>1.0.0</code></li>
<li>한번 빌드 할때 마다, +1씩 해줘야함</li>
</ul>
<pre><code class="language-yaml">    # pubspec.yaml
    version: 1.0.0+1</code></pre>
<ul>
<li>버전번호 : <code>MAJOR(주 버전)</code> . <code>MINOR(부 버전)</code> . <code>PATCH(수정 버전)</code></li>
</ul>
<hr>
<h3 id="8-앱-번들-만들기">8. 앱 번들 만들기</h3>
<ul>
<li>VSCode &gt; Terminal &gt; <code>flutter build ios</code></li>
<li>Xcode &gt; Product &gt; Archive 진행
  <img src="https://velog.velcdn.com/images/devlp_world/post/ad405db6-999d-4004-b488-736c03d3b150/image.png" alt=""></li>
</ul>
<hr>
<h3 id="9-앱-번들-업로드">9. 앱 번들 업로드</h3>
<ul>
<li>빌드와 함께 ipa 파일 생성, Organizer 화면 표시</li>
<li>버전 업데이트 됐는지 확인 필수!
<img src="https://velog.velcdn.com/images/devlp_world/post/9f56d7e7-24c8-4d9f-951e-e45665bed4a5/image.png" alt=""></li>
<li>App Store Connect → Distribute</li>
<li>App Store Connect에 등록됨
(약간의 시간 소요, 진행 상황 메일로 확인 가능)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Flutter] Firebase 로그인 : Apple]]></title>
            <link>https://velog.io/@devlp_world/Firebase-%EB%A1%9C%EA%B7%B8%EC%9D%B8-Apple</link>
            <guid>https://velog.io/@devlp_world/Firebase-%EB%A1%9C%EA%B7%B8%EC%9D%B8-Apple</guid>
            <pubDate>Sun, 23 Mar 2025 06:08:54 GMT</pubDate>
            <description><![CDATA[<h3 id="사전-준비">사전 준비</h3>
<h4 id="1-firebase-설정">1. Firebase 설정</h4>
<ul>
<li>Authentication &gt; 로그인 방법 &gt; 새 제공 업체 추가
<img src="https://velog.velcdn.com/images/devlp_world/post/c978408a-9d7e-4a5a-9fc0-239ee69ad6bf/image.png" alt=""></li>
</ul>
<h4 id="2--xcode-설정">2.  Xcode 설정</h4>
<ol>
<li>Runner &gt; Signing &amp; Capabilities</li>
<li>Team 설정하고, build 진행 → <code>identifier</code> 생성</li>
<li><code>+Capability</code> 클릭 &gt; Sign in with Apple 추가<br><img src="https://velog.velcdn.com/images/devlp_world/post/7c551d24-e23c-4974-a038-8f884b94fe7c/image.png" alt=""></li>
</ol>
<h4 id="3-apple-developer-설정">3. Apple Developer 설정</h4>
<ul>
<li>[Identifiers] App IDs 등록<ol>
<li>Register a new identifier
<img src="https://velog.velcdn.com/images/devlp_world/post/39f21a67-d8ea-4bd1-a10a-0252c802c85a/image.png" alt=""></li>
<li>Select a type
<img src="https://velog.velcdn.com/images/devlp_world/post/4300f02c-841a-4c6a-87c8-f95d2033d010/image.png" alt=""></li>
<li>Register an App ID &gt; Capabilities :  Sign In with Apple 활성화
<img src="https://velog.velcdn.com/images/devlp_world/post/2294866c-720e-4d66-a11d-eee3fe8d499e/image.png" alt=""></li>
</ol>
</li>
</ul>
<h4 id="4-edit">4. Edit</h4>
<ul>
<li>Enable as a primary App ID 선택 확인</li>
<li>Server-to-Server Notification Endpoint : Firebase apple 로그인 콜백 URL 넣기
<img src="https://velog.velcdn.com/images/devlp_world/post/50f5c967-084e-4e84-b4e2-ef31005d8cf2/image.png" alt="">   </li>
</ul>
<hr>
<h3 id="2-keys-새로운-키-생성">2. [Keys] 새로운 키 생성</h3>
<h4 id="1-sign-in-with-apple-추가-후-저장">1. Sign in with Apple 추가 후 저장</h4>
<p><img src="https://velog.velcdn.com/images/devlp_world/post/c4fbe571-e24b-4e67-aea7-1b1011155554/image.png" alt=""></p>
<h4 id="2-sign-in-with-apple--edit">2. Sign in with Apple &gt; Edit</h4>
<ul>
<li>방금 생성한 app id 선택 후 Save
<img src="https://velog.velcdn.com/images/devlp_world/post/bc79523a-e28e-47d1-876e-b63757c99042/image.png" alt=""></li>
<li>P8 파일 다운로드 후, Firebase 비공개 키에 저장</li>
<li>해당 파일은 터미널에서 cat ~, 또는 Chrome에 가져가면 내부 코드 확인 가능
<img src="https://velog.velcdn.com/images/devlp_world/post/5f4fb964-1a66-4ff8-9b56-5b1c0b0244c4/image.png" alt=""></li>
</ul>
<h4 id="3-identifiers-service-ids-등록">3. [Identifiers] Service IDs 등록</h4>
<ul>
<li>Description 입력</li>
<li>Identifier : Bundle ID 역순으로 입력<blockquote>
<p>ex)</p>
</blockquote>
</li>
<li>Bundle ID : com.temp.test</li>
<li>Identifier : test.temp.com</li>
</ul>
<ul>
<li>Sign in with Apple 선택 후, Configure 선택<ul>
<li>일치하는 Primary App Id 선택 후, Website URLs 작성
<img src="https://velog.velcdn.com/images/devlp_world/post/da19e1e9-3a43-4d83-87ac-92c2457a13a3/image.png" alt=""></li>
<li>domain에는 Firebase에서 제공된 Callback URL의 도메인만 입력</li>
<li>Returns URLs 에는 callbackURL 전체 입력
<img src="https://velog.velcdn.com/images/devlp_world/post/0b3b7f46-b549-4424-bb42-040412a924f4/image.png" alt=""></li>
</ul>
</li>
</ul>
<hr>
<h3 id="3-firebase-console-설정">3. Firebase Console 설정</h3>
<ul>
<li><p>Authentication &gt; 로그인 방법 &gt; 새 제공업체 추가 &gt; Apple 추가</p>
<ul>
<li><p>안드로이드 애플 로그인을 위해, OAuth 코드 흐름 구성 설정</p>
<blockquote>
<ol>
<li>Apple 팀 ID 입력 : Apple Developer 주식회사 이름 뒤에 있는 문자</li>
<li>키 ID 입력</li>
<li>비공개 키 입력</li>
</ol>
<p>-<code>Apple Developer 설정 &gt;  [Keys] 새로운 키 생성</code> 에서 다운 받은 키<br><img src="https://velog.velcdn.com/images/devlp_world/post/0c9f9940-27f4-47c7-a38d-16ff8c33229c/image.png" alt=""></p>
</blockquote>
</li>
</ul>
</li>
</ul>
<h4 id="4-android-설정">4. Android 설정</h4>
<p>[이슈 상황]
안드로이드 - 네이버 앱 apple 로그인 시, 네이버 앱이 백그라운드로 돌아가지 않는 이슈 발생</p>
<p>[해결 방법]</p>
<ul>
<li><p>AndroidManifest.xml 수정</p>
<pre><code class="language-xml">  &lt;!-- taskAffinity=&quot;&quot; 에서 테스크 이름 설정해줌 --&gt;
  &lt;activity
      android:name=&quot;.MainActivity&quot;
      android:exported=&quot;true&quot;
      android:launchMode=&quot;singleTop&quot;
      android:taskAffinity=&quot;&quot;
      android:theme=&quot;@style/LaunchTheme&quot;
      android:configChanges=&quot;orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode&quot;
      android:hardwareAccelerated=&quot;true&quot;
      android:windowSoftInputMode=&quot;adjustResize&quot;&gt;</code></pre>
</li>
</ul>
<hr>
<h3 id="코드-구현-예시">코드 구현 예시</h3>
<pre><code class="language-dart">Future&lt;UserCredential&gt; signInWithApple() async {
  AppleAuthProvider appleProvider =
      AppleAuthProvider().addScope(&#39;email&#39;).addScope(&#39;name&#39;);
  UserCredential userCredential =
      await FirebaseAuth.instance.signInWithProvider(appleProvider);
  return userCredential;
}</code></pre>
<hr>
<h3 id="🔗-ref">🔗 Ref</h3>
<ul>
<li><a href="https://wlrn566.tistory.com/141">https://wlrn566.tistory.com/141</a>
<a href="https://medium.com/@saqwzx88/%EC%95%A1%ED%8B%B0%EB%B9%84%ED%8B%B0-%EC%8A%A4%ED%83%9D-%EA%B4%80%EB%A6%AC-3e5219510ac7">https://medium.com/@saqwzx88/%EC%95%A1%ED%8B%B0%EB%B9%84%ED%8B%B0-%EC%8A%A4%ED%83%9D-%EA%B4%80%EB%A6%AC-3e5219510ac7</a></li>
<li><a href="https://medium.com/@saqwzx88/%EC%95%A1%ED%8B%B0%EB%B9%84%ED%8B%B0-%EC%8A%A4%ED%83%9D-%EA%B4%80%EB%A6%AC-3e5219510ac7">https://medium.com/@saqwzx88/%EC%95%A1%ED%8B%B0%EB%B9%84%ED%8B%B0-%EC%8A%A4%ED%83%9D-%EA%B4%80%EB%A6%AC-3e5219510ac7</a></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>