Jekyll2020-08-19T05:28:18+00:00https://fkkmemi.github.io/feed.xmlfkkmemidev.logs --savememifkkmemi@gmail.comFlutter와 Firebase로 Android iOS 둘 다 만들기 25 사용자 권한 처리하기2020-04-13T00:00:00+00:002020-04-13T00:00:00+00:00https://fkkmemi.github.io/ff/ff%20025<p>사용자 권한 별로 다른 화면을 표시합니다.</p>
<h1 id="개요">개요</h1>
<p>로그인해서 사용자별 다른 액션을 취하려면 firebase auth user로는 정보가 부족합니다.</p>
<p>firebase auth user는 권한, 그룹등의 관리용 데이터를 넣을 수 없습니다.</p>
<p>그래서 파이어베이스 유저의 uid를 사용해서 사용자 관리 데이터베이스를 만들어야합니다.</p>
<p>그러려면 파이어스토어에 정보를 기입해야되는데 동기화 문제와 관리 문제가 있습니다.</p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="n">user</span> <span class="o">=</span> <span class="n">await</span> <span class="n">_googleSignIn</span><span class="o">();</span>
<span class="n">await</span> <span class="n">Firestore</span><span class="o">.</span><span class="na">instance</span><span class="o">.</span><span class="na">collection</span><span class="o">(</span><span class="s">'users'</span><span class="o">).</span><span class="na">document</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">uid</span><span class="o">).</span><span class="na">setData</span><span class="o">({</span> <span class="s">'email'</span><span class="o">:</span> <span class="n">user</span><span class="o">.</span><span class="na">email</span><span class="o">,</span> <span class="s">'level'</span><span class="o">:</span> <span class="mi">3</span> <span class="o">});</span>
</code></pre></div></div>
<p>예를 들어 위와 같이 회원가입과 동시에 uid로 정보를 넣을 때 파이어스토어에 잘 들어가기를 희망하지만.. 인터넷 상황과 메모리부족등의 다양한 이유로 데이터가 못 들어갈 수도 있습니다.</p>
<p>그렇게 될 경우가 있기 때문에 확인 로직등이 구차하게 많이 들어가야합니다.</p>
<p>특히 사용자를 삭제할 때 파이어베이스 사용자는 지웠는데 파이어스토어 데이터는 못지울 경우도 있기 때문에 사용자의 생성, 삭제 이벤트를 받아서 서버에서 처리하는 것이 좋습니다.</p>
<h1 id="관리웹">관리웹</h1>
<p>사용자 관리를 하려면 웹이 가장 적절하다고 생각합니다.</p>
<p>관리웹을 만드실 거면 아래 링크로 만들어보면 됩니다.</p>
<p><a href="/vf">Vue와 Firebase로 모던웹사이트 만들기</a></p>
<p>관리웹이 어렵다면 간단하게 백그라운드 이벤트 처리가 가능한 functions만 사용하면 됩니다.</p>
<h1 id="계획">계획</h1>
<ul>
<li>사용자 권한(level) 낮을 수록 높은 권한</li>
<li>사용자 생성시 권한 초기값은 5</li>
<li>관리자만 사용자 권한을 변경 가능</li>
</ul>
<blockquote>
<p>기호에 맞게 하시면 됩니다.</p>
</blockquote>
<h1 id="firebase-functions-사용하기">firebase functions 사용하기</h1>
<p>파이어베이스 펑션스는 기본적으로 REST API를 처리하는 용도로 사용하지만 다양한 이벤트 처리도 할 수 있습니다.(<em>파이어스토어의 변경 감지, 사용자 생성, 삭제 이벤트등</em>)</p>
<p>설치는 <a href="/vf/vf-001/">Vue와 Firebase로 모던웹사이트 만들기 1 개발환경 구축하기</a> 에서 설명했기 때문에 사용자 이벤트 부분만 간단히 요약합니다.</p>
<h2 id="펑션스-이벤트-만들기">펑션스 이벤트 만들기</h2>
<p>참고: <a href="https://firebase.google.com/docs/auth/extend-with-functions?authuser=0">https://firebase.google.com/docs/auth/extend-with-functions?authuser=0</a></p>
<p>펑션스는 사용자가 생성, 삭제 될 때 사용자 정보를 얻을 수 있습니다.</p>
<p>이때 파이어스토어에 필요한 기본 정보를 넣어줍니다.</p>
<p><strong>functions/index.js</strong></p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">functions</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">firebase-functions</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">admin</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">firebase-admin</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">serviceAccount</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">./test-ff6-firebase-adminsdk-u40fa-d02d4e5a01.json</span><span class="dl">'</span><span class="p">)</span>
<span class="nx">admin</span><span class="p">.</span><span class="nx">initializeApp</span><span class="p">({</span>
<span class="na">credential</span><span class="p">:</span> <span class="nx">admin</span><span class="p">.</span><span class="nx">credential</span><span class="p">.</span><span class="nx">cert</span><span class="p">(</span><span class="nx">serviceAccount</span><span class="p">),</span>
<span class="na">databaseURL</span><span class="p">:</span> <span class="dl">'</span><span class="s1">https://test-ff6.firebaseio.com</span><span class="dl">'</span>
<span class="p">})</span>
<span class="nx">exports</span><span class="p">.</span><span class="nx">createUser</span> <span class="o">=</span> <span class="nx">functions</span><span class="p">.</span><span class="nx">auth</span><span class="p">.</span><span class="nx">user</span><span class="p">().</span><span class="nx">onCreate</span><span class="p">(</span><span class="k">async</span> <span class="p">(</span><span class="nx">user</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">email</span><span class="p">:</span> <span class="nx">user</span><span class="p">.</span><span class="nx">email</span><span class="p">,</span>
<span class="na">level</span><span class="p">:</span> <span class="mi">5</span>
<span class="p">}</span>
<span class="nx">admin</span><span class="p">.</span><span class="nx">firestore</span><span class="p">().</span><span class="nx">collection</span><span class="p">(</span><span class="dl">'</span><span class="s1">users</span><span class="dl">'</span><span class="p">).</span><span class="nx">doc</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">uid</span><span class="p">).</span><span class="kd">set</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span>
<span class="p">})</span>
<span class="nx">exports</span><span class="p">.</span><span class="nx">deleteUser</span> <span class="o">=</span> <span class="nx">functions</span><span class="p">.</span><span class="nx">auth</span><span class="p">.</span><span class="nx">user</span><span class="p">().</span><span class="nx">onDelete</span><span class="p">(</span><span class="k">async</span> <span class="p">(</span><span class="nx">user</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">admin</span><span class="p">.</span><span class="nx">firestore</span><span class="p">().</span><span class="nx">collection</span><span class="p">(</span><span class="dl">'</span><span class="s1">users</span><span class="dl">'</span><span class="p">).</span><span class="nx">doc</span><span class="p">(</span><span class="nx">user</span><span class="p">.</span><span class="nx">uid</span><span class="p">).</span><span class="k">delete</span><span class="p">()</span>
<span class="p">})</span>
</code></pre></div></div>
<p>이제 어떤 플랫폼에서든 생성과 삭제시 알아서 파이어스토어에 생성되고 삭제됩니다.</p>
<p><strong>배포</strong></p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>firebase deploy <span class="nt">--only</span> functions
</code></pre></div></div>
<h2 id="룰-설정하기">룰 설정하기</h2>
<p>파이어스토어는 자유롭게 쓸 수 있는 만큼 위험합니다.</p>
<p>항상 보안에 각별히 신경 써야합니다.</p>
<p>참고: <a href="https://firebase.google.com/docs/firestore/security/insecure-rules?authuser=0">https://firebase.google.com/docs/firestore/security/insecure-rules?authuser=0</a></p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{uid} {
allow read: if uid == request.auth.uid || get(/databases/$(database)/documents/users/$(request.auth.uid)).data.level < 5;
allow create: if false;
allow update: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.level < 4;
allow delete: if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.level == 0;
}
}
}
</code></pre></div></div>
<ul>
<li>read: 본인이 읽거나 레벨이 5보다 작은 계정(<em>최소한 승인은 된 계정 4</em>)</li>
<li>create: 막아놓음</li>
<li>update: 레벨이 4보다 작은 계정이 수정가능(<em>승인도 되고 해당 그룹등을 통제할 수 있는 계정 3</em>)</li>
<li>delete: 최고권한자만 삭제 가능</li>
</ul>
<p>이 보안 설정은 클라이언트에 해당되는 것입니다.</p>
<blockquote>
<p>펑션스는 admin이므로 이런 보안 따위는 무시합니다.</p>
</blockquote>
<blockquote>
<p>get은 결국 해당 데이터를 읽는 것이기 때문에 read 횟수가 1증가합니다. 룰 작성시 과금도 신경써야합니다.</p>
</blockquote>
<p><strong>배포</strong></p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>firebase deploy <span class="nt">--only</span> firestore
</code></pre></div></div>
<h1 id="홈페이지에서-표시">홈페이지에서 표시</h1>
<p><strong>pages/home.dart</strong></p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Widget</span> <span class="nf">_buildBody</span><span class="p">(</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">StreamBuilder</span><span class="o"><</span><span class="n">DocumentSnapshot</span><span class="o">>(</span>
<span class="nl">stream:</span> <span class="n">Firestore</span><span class="o">.</span><span class="na">instance</span><span class="o">.</span><span class="na">collection</span><span class="o">(</span><span class="s">'users'</span><span class="o">).</span><span class="na">document</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">uid</span><span class="o">).</span><span class="na">snapshots</span><span class="o">(),</span>
<span class="nl">initialData:</span> <span class="kc">null</span><span class="o">,</span>
<span class="nl">builder:</span> <span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">snapshot</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">snapshot</span><span class="o">.</span><span class="na">hasData</span><span class="o">)</span> <span class="k">return</span> <span class="n">Center</span><span class="o">(</span><span class="nl">child:</span> <span class="n">CircularProgressIndicator</span><span class="o">());</span>
<span class="n">Map</span><span class="o"><</span><span class="kt">String</span><span class="o">,</span> <span class="kd">dynamic</span><span class="o">></span> <span class="n">u</span> <span class="o">=</span> <span class="n">snapshot</span><span class="o">.</span><span class="na">data</span><span class="o">.</span><span class="na">data</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">u</span><span class="o">[</span><span class="s">'level'</span><span class="o">]</span> <span class="o">></span> <span class="mi">4</span><span class="o">)</span> <span class="k">return</span> <span class="n">Text</span><span class="o">(</span><span class="s">'등급업이 필요합니다'</span><span class="o">);</span>
<span class="k">return</span> <span class="n">Text</span><span class="o">(</span><span class="s">'환영합니다'</span><span class="o">);</span>
<span class="o">},</span>
<span class="o">);</span>
<span class="o">}</span>
<span class="nd">@override</span>
<span class="n">Widget</span> <span class="nf">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Scaffold</span><span class="o">(</span>
<span class="nl">appBar:</span> <span class="n">AppBar</span><span class="o">(</span>
<span class="nl">title:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'HomePage'</span><span class="o">),</span>
<span class="nl">actions:</span> <span class="o"><</span><span class="n">Widget</span><span class="o">>[</span>
<span class="n">_buildProfile</span><span class="o">(</span><span class="n">context</span><span class="o">),</span>
<span class="o">],</span>
<span class="o">),</span>
<span class="nl">body:</span> <span class="n">_buildBody</span><span class="o">(),</span>
<span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>이제 파이어스토어의 변화를 감지해서 화면에 표시할 수 있습니다.</p>
<h2 id="사용자-클래스-만들기">사용자 클래스 만들기</h2>
<p>사용자 클래스를 만들어서 나중을 도모합니다.</p>
<p><a href="/ff/ff-011/">참고</a></p>
<p><strong>models/user.dart</strong></p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:json_annotation/json_annotation.dart'</span><span class="o">;</span>
<span class="kn">part</span> <span class="s">'user.g.dart'</span><span class="o">;</span>
<span class="nd">@JsonSerializable</span><span class="o">()</span>
<span class="kd">class</span> <span class="nc">User</span> <span class="o">{</span>
<span class="kd">final</span> <span class="kt">String</span> <span class="n">email</span><span class="o">;</span>
<span class="kt">int</span> <span class="n">level</span><span class="o">;</span>
<span class="n">User</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">email</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">level</span><span class="o">);</span>
<span class="kd">factory</span> <span class="n">User</span><span class="o">.</span><span class="na">fromJson</span><span class="o">(</span><span class="n">Map</span><span class="o"><</span><span class="kt">String</span><span class="o">,</span> <span class="kd">dynamic</span><span class="o">></span> <span class="n">json</span><span class="o">)</span> <span class="o">=></span> <span class="n">_$UserFromJson</span><span class="o">(</span><span class="n">json</span><span class="o">);</span>
<span class="n">Map</span><span class="o"><</span><span class="kt">String</span><span class="o">,</span> <span class="kd">dynamic</span><span class="o">></span> <span class="n">toJson</span><span class="o">()</span> <span class="o">=></span> <span class="n">_$UserToJson</span><span class="o">(</span><span class="k">this</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>직렬화해서 사용하면 됩니다.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>flutter pub run build_runner build
</code></pre></div></div>
<h2 id="적용하기">적용하기</h2>
<p><strong>pages/home.dart</strong></p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">User</span> <span class="n">u</span> <span class="o">=</span> <span class="n">User</span><span class="o">.</span><span class="na">fromJson</span><span class="o">(</span><span class="n">snapshot</span><span class="o">.</span><span class="na">data</span><span class="o">.</span><span class="na">data</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">u</span><span class="o">.</span><span class="na">level</span> <span class="o">></span> <span class="mi">4</span><span class="o">)</span> <span class="k">return</span> <span class="n">Text</span><span class="o">(</span><span class="s">'등급업이 필요합니다'</span><span class="o">);</span>
</code></pre></div></div>
<h2 id="공용-위젯-만들기">공용 위젯 만들기</h2>
<p>자주 쓰는 위젯을 따로 빼두었습니다.</p>
<p><strong>widgets/loading.dart</strong></p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>
<span class="n">Widget</span> <span class="nf">widgetLoading</span><span class="p">(</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Center</span><span class="o">(</span>
<span class="nl">child:</span> <span class="n">CircularProgressIndicator</span><span class="o">(),</span>
<span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p><strong>widgets/message.dart</strong></p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>
<span class="n">Widget</span> <span class="nf">widgetMessage</span><span class="p">(</span><span class="kt">String</span> <span class="n">message</span><span class="o">,</span> <span class="n">IconData</span> <span class="n">icon</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Center</span><span class="o">(</span>
<span class="nl">child:</span> <span class="n">Column</span><span class="o">(</span>
<span class="nl">mainAxisAlignment:</span> <span class="n">MainAxisAlignment</span><span class="o">.</span><span class="na">center</span><span class="o">,</span>
<span class="nl">children:</span> <span class="o"><</span><span class="n">Widget</span><span class="o">>[</span>
<span class="n">Text</span><span class="o">(</span><span class="n">message</span><span class="o">),</span>
<span class="n">Icon</span><span class="o">(</span><span class="n">icon</span><span class="o">),</span>
<span class="o">],</span>
<span class="o">),</span>
<span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<h2 id="완성">완성</h2>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Widget</span> <span class="nf">_buildBody</span><span class="p">(</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">StreamBuilder</span><span class="o"><</span><span class="n">DocumentSnapshot</span><span class="o">>(</span>
<span class="nl">stream:</span> <span class="n">Firestore</span><span class="o">.</span><span class="na">instance</span><span class="o">.</span><span class="na">collection</span><span class="o">(</span><span class="s">'users'</span><span class="o">).</span><span class="na">document</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">uid</span><span class="o">).</span><span class="na">snapshots</span><span class="o">(),</span>
<span class="nl">initialData:</span> <span class="kc">null</span><span class="o">,</span>
<span class="nl">builder:</span> <span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="n">snapshot</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">snapshot</span><span class="o">.</span><span class="na">hasData</span><span class="o">)</span> <span class="k">return</span> <span class="n">widgetLoading</span><span class="o">();</span>
<span class="n">User</span> <span class="n">u</span> <span class="o">=</span> <span class="n">User</span><span class="o">.</span><span class="na">fromJson</span><span class="o">(</span><span class="n">snapshot</span><span class="o">.</span><span class="na">data</span><span class="o">.</span><span class="na">data</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">u</span><span class="o">.</span><span class="na">level</span> <span class="o">></span> <span class="mi">4</span><span class="o">)</span> <span class="k">return</span> <span class="n">widgetMessage</span><span class="o">(</span><span class="s">'등급업이 필요합니다'</span><span class="o">,</span> <span class="n">Icons</span><span class="o">.</span><span class="na">error_outline</span><span class="o">);</span>
<span class="k">return</span> <span class="n">widgetMessage</span><span class="o">(</span><span class="s">'환영합니다'</span><span class="o">,</span> <span class="n">Icons</span><span class="o">.</span><span class="na">check_circle_outline</span><span class="o">);</span>
<span class="o">},</span>
<span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p><img src="/images/ff/2020-04-14_20.55.09.png" alt="alt fin" /></p>
<h1 id="마치며">마치며</h1>
<p>웹까지 해야되니 괴로울 수도 있지만.. 회원관리를 하려면 어쩔 수 없는 것입니다.</p>
<p>원하는 기능만 사용한다면 웹도 충분히 해볼만합니다.</p>
<p>나중에 푸시를 생각해서라도 모바일을 개발하려면 서버는 꼭 필요합니다.</p>
<blockquote>
<p>앱을 출시할 경우 이용 약관 같은 것도 어짜피 url로 넘기게 되어 있는데 vue&firebase를 조금만 익혀도 손쉽게 해결할 수 있는 문제들입니다.</p>
</blockquote>
<h1 id="소스">소스</h1>
<p>functions: <a href="https://github.com/fkkmemi/ff-web">https://github.com/fkkmemi/ff2</a></p>
<p>flutter: <a href="https://github.com/fkkmemi/ff2">https://github.com/fkkmemi/ff2</a></p>
<h1 id="영상">영상</h1>
<!-- Courtesy of embedresponsively.com //-->
<div class="responsive-video-container">
<iframe src="https://www.youtube-nocookie.com/embed/VrrwHQs5dIE" frameborder="0" allowfullscreen=""></iframe>
</div>memifkkmemi@gmail.com사용자 권한 별로 다른 화면을 표시합니다.Flutter와 Firebase로 Android iOS 둘 다 만들기 24 fire storage에 저장하기2020-04-06T00:00:00+00:002020-04-06T00:00:00+00:00https://fkkmemi.github.io/ff/ff%20024<p>파이어베이스 스토리지에 저장하고 링크를 받아 프로필을 표시해봅니다.</p>
<h1 id="개요">개요</h1>
<p>파이어베이스 스토리지는 파일을 저장할 수 있고 http링크로 다양한 플랫폼에서 액세스가 가능합니다.</p>
<blockquote>
<p>AWS S3와 비슷합니다.</p>
</blockquote>
<p>무료 요금도</p>
<h1 id="파이어베이스-설정">파이어베이스 설정</h1>
<p>파이어베이스 콘솔에가서 스토리지를 활성화시킵니다.</p>
<p><img src="/images/ff/2020-04-06_18.45.07.png" alt="alt init" /></p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if request.auth != null;
}
}
}
</code></pre></div></div>
<p>보안규칙은 기본으로 해두면 로그인한 사람만 액세스가 가능합니다.</p>
<p>이미 로그인 프로세스를 구축해두었기 때문에 간편하게 사용이 가능합니다.</p>
<h1 id="설치">설치</h1>
<p>참고: <a href="https://pub.dev/packages/firebase_storage">https://pub.dev/packages/firebase_storage</a></p>
<p><strong>pubspec.yaml</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> firebase_storage: ^3.1.5
</code></pre></div></div>
<h1 id="업로드-버튼-만들기">업로드 버튼 만들기</h1>
<p>상단의 저장 버튼을 클릭할 때 Image 파일이 있을 경우에만 활성화 되도록 만듭니다.</p>
<p><strong>pages/profile_edit.dart</strong></p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">_save</span> <span class="o">()</span> <span class="n">async</span> <span class="o">{</span>
<span class="n">print</span><span class="o">(</span><span class="s">'save'</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">Widget</span> <span class="nf">_buildSaveButton</span> <span class="p">(</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">IconButton</span><span class="o">(</span>
<span class="nl">icon:</span> <span class="n">Icon</span><span class="o">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">save</span><span class="o">),</span>
<span class="nl">onPressed:</span> <span class="n">_image</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">?</span> <span class="kc">null</span> <span class="o">:</span> <span class="o">()</span> <span class="n">async</span> <span class="o">=></span> <span class="n">_save</span><span class="o">(),</span>
<span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>onPressed가 null 일 경우 버튼은 자동으로 disabled 상태가 되며 동작하지 않게 됩니다.</p>
<h1 id="업로드하기">업로드하기</h1>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:firebase_storage/firebase_storage.dart'</span><span class="o">;</span>
<span class="n">_save</span> <span class="o">()</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">StorageReference</span> <span class="n">ref</span> <span class="o">=</span> <span class="n">FirebaseStorage</span><span class="o">().</span><span class="na">ref</span><span class="o">().</span><span class="na">child</span><span class="o">(</span><span class="s">'images'</span><span class="o">).</span><span class="na">child</span><span class="o">(</span><span class="s">'users'</span><span class="o">).</span><span class="na">child</span><span class="o">(</span><span class="n">widget</span><span class="o">.</span><span class="na">user</span><span class="o">.</span><span class="na">uid</span><span class="o">);</span>
<span class="kd">final</span> <span class="n">StorageUploadTask</span> <span class="n">uploadTask</span> <span class="o">=</span> <span class="n">ref</span><span class="o">.</span><span class="na">putFile</span><span class="o">(</span><span class="n">_image</span><span class="o">,);</span>
<span class="n">uploadTask</span><span class="o">.</span><span class="na">events</span><span class="o">.</span><span class="na">listen</span><span class="o">((</span><span class="n">event</span><span class="o">)</span> <span class="o">{</span>
<span class="n">print</span><span class="o">(</span><span class="n">event</span><span class="o">);</span>
<span class="n">print</span><span class="o">(</span><span class="s">'EVENT </span><span class="si">${event.type}</span><span class="s">'</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">type</span> <span class="o">==</span> <span class="n">StorageTaskEventType</span><span class="o">.</span><span class="na">success</span><span class="o">)</span> <span class="o">{</span>
<span class="n">ref</span><span class="o">.</span><span class="na">getDownloadURL</span><span class="o">()</span>
<span class="o">.</span><span class="na">then</span><span class="o">((</span><span class="n">url</span><span class="o">)</span> <span class="n">async</span> <span class="o">{</span>
<span class="n">_image</span><span class="o">.</span><span class="na">deleteSync</span><span class="o">();</span>
<span class="n">print</span><span class="o">(</span><span class="n">url</span><span class="o">);</span>
<span class="kd">final</span> <span class="n">UserUpdateInfo</span> <span class="n">userUpdateInfo</span> <span class="o">=</span> <span class="n">UserUpdateInfo</span><span class="o">();</span>
<span class="n">userUpdateInfo</span><span class="o">.</span><span class="na">photoUrl</span> <span class="o">=</span> <span class="n">url</span><span class="o">;</span>
<span class="n">await</span> <span class="n">widget</span><span class="o">.</span><span class="na">user</span><span class="o">.</span><span class="na">updateProfile</span><span class="o">(</span><span class="n">userUpdateInfo</span><span class="o">);</span>
<span class="n">await</span> <span class="n">widget</span><span class="o">.</span><span class="na">user</span><span class="o">.</span><span class="na">reload</span><span class="o">();</span>
<span class="n">Navigator</span><span class="o">.</span><span class="na">pushNamedAndRemoveUntil</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="s">'/auth'</span><span class="o">,</span> <span class="o">(</span><span class="n">r</span><span class="o">)</span> <span class="o">=></span> <span class="kc">false</span><span class="o">);</span>
<span class="o">});</span>
<span class="o">}</span>
<span class="o">});</span>
<span class="o">}</span>
</code></pre></div></div>
<p>스토리지나 스토어 모두 참조 위치, 즉 레퍼런스가 가장 중요합니다.</p>
<p>참조는 images/users/uid 로 지정했습니다.</p>
<p>레퍼런스의 putFile로 파일이 전송됩니다.</p>
<blockquote>
<p>putFile 뒤에 옵션으로 파일 형식등의 메타 정보를 지정할 수도 있습니다.</p>
</blockquote>
<p>파일이 전송 중에 스트림 구독으로 상태를 알아 낼 수 있습니다.</p>
<blockquote>
<p>사실 플러터답게 꾸미려면 스트림빌더로 사진위젯을 구현하는 것이 좋습니다.</p>
</blockquote>
<p>그 중 이벤트 타입이 성공일 경우 사본 이미지를 지웁니다.</p>
<p>그리고 프로필 업데이트 후에 /auth로 보내버립니다.</p>
<blockquote>
<p>보내는 것이 맘에 안들면 FirebaseAuth.instance.currentUser()로 치환해주는 방법이 있습니다.</p>
</blockquote>
<h1 id="이름-저장하기">이름 저장하기</h1>
<p>빈 이름등을 방지하기 위해 지난번에 했던 폼관련 작업을 해둡니다.</p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="n">TextEditingController</span> <span class="n">firstNameInputController</span><span class="o">;</span>
<span class="n">TextEditingController</span> <span class="n">lastNameInputController</span><span class="o">;</span>
<span class="kd">final</span> <span class="n">GlobalKey</span><span class="o"><</span><span class="n">FormState</span><span class="o">></span> <span class="n">_formKey</span> <span class="o">=</span> <span class="n">GlobalKey</span><span class="o"><</span><span class="n">FormState</span><span class="o">>();</span>
<span class="kt">bool</span> <span class="n">_nameChanged</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="kt">bool</span> <span class="n">_loading</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="nd">@override</span>
<span class="kt">void</span> <span class="n">initState</span><span class="o">()</span> <span class="o">{</span>
<span class="k">super</span><span class="o">.</span><span class="na">initState</span><span class="o">();</span>
<span class="kd">final</span> <span class="n">names</span> <span class="o">=</span> <span class="n">widget</span><span class="o">.</span><span class="na">user</span><span class="o">.</span><span class="na">displayName</span><span class="o">.</span><span class="na">split</span><span class="o">(</span><span class="s">' '</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">names</span><span class="o">.</span><span class="na">length</span> <span class="o"><</span> <span class="mi">2</span><span class="o">)</span> <span class="o">{</span>
<span class="n">firstNameInputController</span> <span class="o">=</span> <span class="n">TextEditingController</span><span class="o">();</span>
<span class="n">lastNameInputController</span> <span class="o">=</span> <span class="n">TextEditingController</span><span class="o">();</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">firstNameInputController</span> <span class="o">=</span> <span class="n">TextEditingController</span><span class="o">(</span><span class="nl">text:</span> <span class="n">names</span><span class="o">[</span><span class="mi">0</span><span class="o">]);</span>
<span class="n">lastNameInputController</span> <span class="o">=</span> <span class="n">TextEditingController</span><span class="o">(</span><span class="nl">text:</span> <span class="n">names</span><span class="o">[</span><span class="mi">1</span><span class="o">]);</span>
<span class="n">firstNameInputController</span><span class="o">.</span><span class="na">addListener</span><span class="o">(()</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">firstNameInputController</span><span class="o">.</span><span class="na">text</span> <span class="o">==</span> <span class="n">names</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span> <span class="o">&&</span> <span class="n">lastNameInputController</span><span class="o">.</span><span class="na">text</span> <span class="o">==</span> <span class="n">names</span><span class="o">[</span><span class="mi">1</span><span class="o">])</span> <span class="n">setState</span><span class="o">(()</span> <span class="o">=></span> <span class="n">_nameChanged</span> <span class="o">=</span> <span class="kc">false</span><span class="o">);</span>
<span class="k">else</span> <span class="n">setState</span><span class="o">(()</span> <span class="o">=></span> <span class="n">_nameChanged</span> <span class="o">=</span> <span class="kc">true</span><span class="o">);</span>
<span class="o">});</span>
<span class="n">lastNameInputController</span><span class="o">.</span><span class="na">addListener</span><span class="o">(()</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">firstNameInputController</span><span class="o">.</span><span class="na">text</span> <span class="o">==</span> <span class="n">names</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span> <span class="o">&&</span> <span class="n">lastNameInputController</span><span class="o">.</span><span class="na">text</span> <span class="o">==</span> <span class="n">names</span><span class="o">[</span><span class="mi">1</span><span class="o">])</span> <span class="n">setState</span><span class="o">(()</span> <span class="o">=></span> <span class="n">_nameChanged</span> <span class="o">=</span> <span class="kc">false</span><span class="o">);</span>
<span class="k">else</span> <span class="n">setState</span><span class="o">(()</span> <span class="o">=></span> <span class="n">_nameChanged</span> <span class="o">=</span> <span class="kc">true</span><span class="o">);</span>
<span class="o">});</span>
<span class="o">}</span>
<span class="nd">@override</span>
<span class="kt">void</span> <span class="n">dispose</span><span class="o">()</span> <span class="o">{</span>
<span class="n">firstNameInputController</span><span class="o">.</span><span class="na">dispose</span><span class="o">();</span>
<span class="n">lastNameInputController</span><span class="o">.</span><span class="na">dispose</span><span class="o">();</span>
<span class="k">super</span><span class="o">.</span><span class="na">dispose</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>
<p>_nameChanged를 사용해서 저장버튼 상태를 표시합니다.</p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Widget</span> <span class="nf">_buildSaveButton</span> <span class="p">(</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">_loading</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Center</span><span class="o">(</span>
<span class="nl">child:</span> <span class="n">Container</span><span class="o">(</span>
<span class="nl">width:</span> <span class="mi">40</span><span class="o">,</span>
<span class="nl">height:</span> <span class="mi">40</span><span class="o">,</span>
<span class="nl">padding:</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="o">(</span><span class="mi">8</span><span class="o">),</span>
<span class="nl">child:</span> <span class="n">CircularProgressIndicator</span><span class="o">(</span><span class="nl">backgroundColor:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">white</span><span class="o">,),</span>
<span class="o">)</span>
<span class="o">);</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">IconButton</span><span class="o">(</span>
<span class="nl">icon:</span> <span class="n">Icon</span><span class="o">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">save</span><span class="o">),</span>
<span class="nl">onPressed:</span> <span class="n">_image</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">||</span> <span class="n">_nameChanged</span> <span class="o">?</span> <span class="o">()</span> <span class="o">=></span> <span class="n">_save</span><span class="o">()</span> <span class="o">:</span> <span class="kc">null</span><span class="o">,</span>
<span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>이미지가 있거나 변한 것이 있을 경우만 저장버튼이 활성화됩니다.</p>
<h1 id="최종-저장">최종 저장</h1>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">_save</span> <span class="o">()</span> <span class="n">async</span> <span class="o">{</span>
<span class="n">setState</span><span class="o">(()</span> <span class="o">=></span> <span class="n">_loading</span> <span class="o">=</span> <span class="kc">true</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">_image</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">StorageReference</span> <span class="n">ref</span> <span class="o">=</span> <span class="n">FirebaseStorage</span><span class="o">().</span><span class="na">ref</span><span class="o">().</span><span class="na">child</span><span class="o">(</span><span class="s">'images'</span><span class="o">).</span><span class="na">child</span><span class="o">(</span><span class="s">'users'</span><span class="o">).</span><span class="na">child</span><span class="o">(</span><span class="n">widget</span><span class="o">.</span><span class="na">user</span><span class="o">.</span><span class="na">uid</span><span class="o">);</span>
<span class="kd">final</span> <span class="n">StorageUploadTask</span> <span class="n">uploadTask</span> <span class="o">=</span> <span class="n">ref</span><span class="o">.</span><span class="na">putFile</span><span class="o">(</span><span class="n">_image</span><span class="o">,);</span>
<span class="n">uploadTask</span><span class="o">.</span><span class="na">events</span><span class="o">.</span><span class="na">listen</span><span class="o">((</span><span class="n">event</span><span class="o">)</span> <span class="o">{</span>
<span class="n">print</span><span class="o">(</span><span class="n">event</span><span class="o">);</span>
<span class="n">print</span><span class="o">(</span><span class="s">'EVENT </span><span class="si">${event.type}</span><span class="s">'</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">event</span><span class="o">.</span><span class="na">type</span> <span class="o">==</span> <span class="n">StorageTaskEventType</span><span class="o">.</span><span class="na">success</span><span class="o">)</span> <span class="o">{</span>
<span class="n">ref</span><span class="o">.</span><span class="na">getDownloadURL</span><span class="o">()</span>
<span class="o">.</span><span class="na">then</span><span class="o">((</span><span class="n">url</span><span class="o">)</span> <span class="n">async</span> <span class="o">{</span>
<span class="n">_image</span><span class="o">.</span><span class="na">deleteSync</span><span class="o">();</span>
<span class="kd">final</span> <span class="n">UserUpdateInfo</span> <span class="n">userUpdateInfo</span> <span class="o">=</span> <span class="n">UserUpdateInfo</span><span class="o">();</span>
<span class="n">userUpdateInfo</span><span class="o">.</span><span class="na">photoUrl</span> <span class="o">=</span> <span class="n">url</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">_nameChanged</span><span class="o">)</span> <span class="n">userUpdateInfo</span><span class="o">.</span><span class="na">displayName</span> <span class="o">=</span> <span class="n">firstNameInputController</span><span class="o">.</span><span class="na">text</span> <span class="o">+</span> <span class="s">' '</span> <span class="o">+</span> <span class="n">lastNameInputController</span><span class="o">.</span><span class="na">text</span><span class="o">;</span>
<span class="n">await</span> <span class="n">widget</span><span class="o">.</span><span class="na">user</span><span class="o">.</span><span class="na">updateProfile</span><span class="o">(</span><span class="n">userUpdateInfo</span><span class="o">);</span>
<span class="n">await</span> <span class="n">widget</span><span class="o">.</span><span class="na">user</span><span class="o">.</span><span class="na">reload</span><span class="o">();</span>
<span class="n">setState</span><span class="o">(()</span> <span class="o">=></span> <span class="n">_loading</span> <span class="o">=</span> <span class="kc">false</span><span class="o">);</span>
<span class="n">Navigator</span><span class="o">.</span><span class="na">pushNamedAndRemoveUntil</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="s">'/auth'</span><span class="o">,</span> <span class="o">(</span><span class="n">r</span><span class="o">)</span> <span class="o">=></span> <span class="kc">false</span><span class="o">);</span>
<span class="o">});</span>
<span class="o">}</span>
<span class="o">});</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">UserUpdateInfo</span> <span class="n">userUpdateInfo</span> <span class="o">=</span> <span class="n">UserUpdateInfo</span><span class="o">();</span>
<span class="n">userUpdateInfo</span><span class="o">.</span><span class="na">displayName</span> <span class="o">=</span> <span class="n">firstNameInputController</span><span class="o">.</span><span class="na">text</span> <span class="o">+</span> <span class="s">' '</span> <span class="o">+</span> <span class="n">lastNameInputController</span><span class="o">.</span><span class="na">text</span><span class="o">;</span>
<span class="n">await</span> <span class="n">widget</span><span class="o">.</span><span class="na">user</span><span class="o">.</span><span class="na">updateProfile</span><span class="o">(</span><span class="n">userUpdateInfo</span><span class="o">);</span>
<span class="n">await</span> <span class="n">widget</span><span class="o">.</span><span class="na">user</span><span class="o">.</span><span class="na">reload</span><span class="o">();</span>
<span class="n">setState</span><span class="o">(()</span> <span class="o">=></span> <span class="n">_loading</span> <span class="o">=</span> <span class="kc">false</span><span class="o">);</span>
<span class="n">Navigator</span><span class="o">.</span><span class="na">pushNamedAndRemoveUntil</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="s">'/auth'</span><span class="o">,</span> <span class="o">(</span><span class="n">r</span><span class="o">)</span> <span class="o">=></span> <span class="kc">false</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>이미지가 있을 때는 전송이 끝난 후 프로필 업데이트를 하고 없을 경우 이름만 변경하여 프로필 업데이트합니다.</p>
<h1 id="마치며">마치며</h1>
<p>이것으로 파이어베이스를 이용한 기초적인 운영은 끝입니다.</p>
<p>예제와는 별도로 Fire auth, store, storage를 직접 콘트롤 해보시기 바랍니다.</p>
<p>다음에는 응용편으로 진행합니다.</p>
<h1 id="소스">소스</h1>
<p><a href="https://github.com/fkkmemi/ff2">https://github.com/fkkmemi/ff2</a></p>
<h1 id="영상">영상</h1>
<!-- Courtesy of embedresponsively.com //-->
<div class="responsive-video-container">
<iframe src="https://www.youtube-nocookie.com/embed/MvIbzmlhkeg" frameborder="0" allowfullscreen=""></iframe>
</div>memifkkmemi@gmail.com파이어베이스 스토리지에 저장하고 링크를 받아 프로필을 표시해봅니다.Flutter와 Firebase로 Android iOS 둘 다 만들기 23 사진 가져오고 압축하기2020-04-03T00:00:00+00:002020-04-03T00:00:00+00:00https://fkkmemi.github.io/ff/ff%20023<p>플러터 image_picker 모듈을 사용해서 카메라의 사진 혹은 내장 앨범의 사진을 가져오고 압축해서 표시해봅니다.</p>
<h1 id="개요">개요</h1>
<p>image_picker를 사용하면 iOS android 모두 쉽게 사진을 가져올 수 있습니다.</p>
<p>참고: <a href="https://pub.dev/packages/image_picker">https://pub.dev/packages/image_picker</a></p>
<p>가져오는 것은 쉽지만, 최근 핸드폰들이 성능이 좋아져서 대부분 3,4메가는 쉽게 넘깁니다.</p>
<p>결국 서버(fireStorage)에 3,4메가나 올리는 것은 서버 비용도 부담이고 매번 로드할때 너무 느리기 때문에 이미지를 압축해서 표시합니다.</p>
<h1 id="사진-가져오기">사진 가져오기</h1>
<h2 id="설치">설치</h2>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">dependencies</span><span class="pi">:</span>
<span class="na">flutter</span><span class="pi">:</span>
<span class="na">sdk</span><span class="pi">:</span> <span class="s">flutter</span>
<span class="c1"># ..</span>
<span class="na">image_picker</span><span class="pi">:</span> <span class="s">^0.6.3+4</span>
</code></pre></div></div>
<h2 id="ios-권한-부여">ios 권한 부여</h2>
<p>android의 경우 권한이 따로 필요 없지만 iOS의 경우 카메라 및 앨범 접근시 확인 창을 눌러서 진행하게 됩니다.</p>
<ul>
<li>NSCameraUsageDescription: 카메라 권한</li>
<li>NSPhotoLibraryUsageDescription: 앨범 권한</li>
</ul>
<p><strong>ios/Runner/info.plist</strong></p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <key>LSRequiresIPhoneOS</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>You can change your profile picture</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>You can change your profile picture</string>
<key>UIBackgroundModes</key>
<array>
</code></pre></div></div>
<p>xcode를 열어서 해도 되고 info.plist파일에 직접 넣어도 됩니다.</p>
<blockquote>
<p>어디쯤인지 감이 안오시는 분들을 위해 코드 위아래 일부를 발췌합니다.</p>
</blockquote>
<h2 id="준비">준비</h2>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:image_picker/image_picker.dart'</span><span class="o">;</span>
<span class="kn">import</span> <span class="s">'dart:io'</span><span class="o">;</span>
</code></pre></div></div>
<p>이미지는 결국 파일 입니다. 파일을 사용하기 위해 dart:io가 필요합니다.</p>
<h2 id="함수-만들기">함수 만들기</h2>
<p><strong>pages/profile_edit.dart</strong></p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">_ProfileEditPageState</span> <span class="kd">extends</span> <span class="n">State</span><span class="o"><</span><span class="n">ProfileEditPage</span><span class="o">></span> <span class="o">{</span>
<span class="n">File</span> <span class="n">_image</span><span class="o">;</span>
<span class="n">Future</span> <span class="n">getImage</span><span class="o">(</span><span class="n">ImageSource</span> <span class="kn">source</span><span class="o">)</span> <span class="n">async</span> <span class="o">{</span>
<span class="n">File</span> <span class="n">image</span> <span class="o">=</span> <span class="n">await</span> <span class="n">ImagePicker</span><span class="o">.</span><span class="na">pickImage</span><span class="o">(</span><span class="kn">source</span><span class="o">:</span> <span class="kn">source</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">image</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="k">return</span><span class="o">;</span>
<span class="n">setState</span><span class="o">(()</span> <span class="o">=></span> <span class="n">_image</span> <span class="o">=</span> <span class="n">image</span><span class="o">);</span>
<span class="o">}</span>
<span class="c1">// ..</span>
<span class="o">}</span>
</code></pre></div></div>
<p>ImageSource만 변경해서 카메라와 앨범에서 이미지, 결국 파일을 가져올 수 있습니다.</p>
<p>이미지를 선택하지 않았을 때 null이기 때문에 아래로 내려가지 못하게 막아줍니다.</p>
<h2 id="버튼-꾸미기">버튼 꾸미기</h2>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Widget</span> <span class="nf">_buildCameraButton</span><span class="p">(</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">IconButton</span><span class="o">(</span>
<span class="nl">alignment:</span> <span class="n">Alignment</span><span class="o">.</span><span class="na">bottomRight</span><span class="o">,</span>
<span class="nl">padding:</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="o">(</span><span class="mi">0</span><span class="o">),</span>
<span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">white</span><span class="o">,</span>
<span class="nl">icon:</span> <span class="n">Icon</span><span class="o">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">photo_camera</span><span class="o">),</span>
<span class="nl">onPressed:</span> <span class="o">()</span> <span class="n">async</span> <span class="o">{</span>
<span class="n">await</span> <span class="n">getImage</span><span class="o">(</span><span class="n">ImageSource</span><span class="o">.</span><span class="na">camera</span><span class="o">);</span>
<span class="o">},</span>
<span class="o">);</span>
<span class="o">}</span>
<span class="n">Widget</span> <span class="nf">_buildPhotoAlbumButton</span><span class="p">(</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">IconButton</span><span class="o">(</span>
<span class="nl">alignment:</span> <span class="n">Alignment</span><span class="o">.</span><span class="na">bottomRight</span><span class="o">,</span>
<span class="nl">padding:</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="o">(</span><span class="mi">0</span><span class="o">),</span>
<span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">white</span><span class="o">,</span>
<span class="nl">icon:</span> <span class="n">Icon</span><span class="o">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">photo_library</span><span class="o">),</span>
<span class="nl">onPressed:</span> <span class="o">()</span> <span class="n">async</span> <span class="o">{</span>
<span class="n">await</span> <span class="n">getImage</span><span class="o">(</span><span class="n">ImageSource</span><span class="o">.</span><span class="na">gallery</span><span class="o">);</span>
<span class="o">},</span>
<span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p><img src="/images/ff/2020-04-03_17.57.19.png" alt="alt button" /></p>
<p>디버그 툴로 보면 IconButton이 엄청 크다는 걸 알수 있습니다.</p>
<p>그래서 alignment, padding으로 구석으로 몰아 넣었지만.. 보기 좋지 못합니다.</p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Widget</span> <span class="nf">_buildCameraButton</span><span class="p">(</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">InkWell</span><span class="o">(</span>
<span class="nl">child:</span> <span class="n">Icon</span><span class="o">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">photo_camera</span><span class="o">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">white</span><span class="o">,),</span>
<span class="nl">onTap:</span> <span class="o">()</span> <span class="n">async</span> <span class="o">{</span>
<span class="n">await</span> <span class="n">getImage</span><span class="o">(</span><span class="n">ImageSource</span><span class="o">.</span><span class="na">camera</span><span class="o">);</span>
<span class="o">},</span>
<span class="o">);</span>
<span class="o">}</span>
<span class="n">Widget</span> <span class="nf">_buildPhotoAlbumButton</span><span class="p">(</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">InkWell</span><span class="o">(</span>
<span class="nl">child:</span> <span class="n">Icon</span><span class="o">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">photo_library</span><span class="o">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">white</span><span class="o">,),</span>
<span class="nl">onTap:</span> <span class="o">()</span> <span class="n">async</span> <span class="o">{</span>
<span class="n">await</span> <span class="n">getImage</span><span class="o">(</span><span class="n">ImageSource</span><span class="o">.</span><span class="na">gallery</span><span class="o">);</span>
<span class="o">},</span>
<span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>그래서 결국 이미지를 InkWell로 덮었습니다.</p>
<p>이미지소스만 다르게 하여 getImage를 호출합니다.</p>
<h2 id="화면에-표시하기">화면에 표시하기</h2>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Widget</span> <span class="nf">_buildPhoto</span><span class="p">(</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Container</span><span class="o">(</span>
<span class="nl">padding:</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="o">(</span><span class="mi">10</span><span class="o">),</span>
<span class="nl">child:</span> <span class="n">Stack</span><span class="o">(</span>
<span class="nl">alignment:</span> <span class="n">Alignment</span><span class="o">.</span><span class="na">bottomCenter</span><span class="o">,</span>
<span class="nl">children:</span> <span class="o"><</span><span class="n">Widget</span><span class="o">>[</span>
<span class="n">Container</span><span class="o">(</span>
<span class="nl">constraints:</span> <span class="n">BoxConstraints</span><span class="o">.</span><span class="na">expand</span><span class="o">(),</span>
<span class="nl">child:</span> <span class="n">_image</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">?</span> <span class="n">Image</span><span class="o">.</span><span class="na">network</span><span class="o">(</span><span class="n">widget</span><span class="o">.</span><span class="na">user</span><span class="o">.</span><span class="na">photoUrl</span><span class="o">,</span> <span class="nl">fit:</span> <span class="n">BoxFit</span><span class="o">.</span><span class="na">cover</span><span class="o">)</span> <span class="o">:</span> <span class="n">Image</span><span class="o">.</span><span class="na">file</span><span class="o">(</span><span class="n">_image</span><span class="o">,</span> <span class="nl">fit:</span> <span class="n">BoxFit</span><span class="o">.</span><span class="na">cover</span><span class="o">),</span>
<span class="o">),</span>
<span class="n">Container</span><span class="o">(</span>
<span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">black54</span><span class="o">,</span>
<span class="nl">height:</span> <span class="mi">40</span><span class="o">,</span>
<span class="o">),</span>
<span class="n">ButtonBar</span><span class="o">(</span>
<span class="nl">alignment:</span> <span class="n">MainAxisAlignment</span><span class="o">.</span><span class="na">end</span><span class="o">,</span>
<span class="nl">children:</span> <span class="o"><</span><span class="n">Widget</span><span class="o">>[</span>
<span class="n">_buildCameraButton</span><span class="o">(),</span>
<span class="n">_buildPhotoAlbumButton</span><span class="o">(),</span>
<span class="o">],</span>
<span class="o">)</span>
<span class="o">],</span>
<span class="o">),</span>
<span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>이미지 파일이 없을 때는 계정의 파일을 보여주고 있을 때는 이미지 파일을 표시합니다.</p>
<h1 id="사진-압축하기">사진 압축하기</h1>
<p>flutter_image_compress를 이용해서 압축합니다.</p>
<p>참고: <a href="https://pub.dev/packages/flutter_image_compress">https://pub.dev/packages/flutter_image_compress</a></p>
<p>압축은 간단하지만 염두해야할 것이 있습니다.</p>
<p>대부분 선택한 파일을 교체하는 것을 원하지는 않을 것입니다.</p>
<p>압축한 후에 특정 장소에 저장해야 하는 것입니다.</p>
<p>그리고 사용하고 나서 쓸모 없는 파일이기 때문에 삭제도 해줘야합니다.</p>
<h2 id="설치-1">설치</h2>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">flutter_image_compress</span><span class="pi">:</span> <span class="s">^0.6.5+1</span>
<span class="na">path_provider</span><span class="pi">:</span> <span class="s">^1.6.5</span>
</code></pre></div></div>
<h2 id="준비-1">준비</h2>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:path_provider/path_provider.dart'</span><span class="o">;</span>
<span class="kn">import</span> <span class="s">'package:path/path.dart'</span> <span class="k">as</span> <span class="n">path</span><span class="o">;</span>
<span class="kn">import</span> <span class="s">'package:flutter_image_compress/flutter_image_compress.dart'</span><span class="o">;</span>
</code></pre></div></div>
<p>저장할 위치 선정을 위해 path_provider가 필요합니다.</p>
<p>그리고 저장한 위치의 경로를 조합하기 위해 path 유틸리티가 필요합니다.</p>
<blockquote>
<p>node.js 의 path와 거의 동일</p>
</blockquote>
<h2 id="임시경로-얻기">임시경로 얻기</h2>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">File</span> <span class="n">image</span> <span class="o">=</span> <span class="n">await</span> <span class="n">ImagePicker</span><span class="o">.</span><span class="na">pickImage</span><span class="o">(</span><span class="kn">source</span><span class="o">:</span> <span class="kn">source</span><span class="o">);</span>
<span class="n">Directory</span> <span class="n">tempDir</span> <span class="o">=</span> <span class="n">await</span> <span class="n">getTemporaryDirectory</span><span class="o">();</span>
<span class="kt">String</span> <span class="n">tempPath</span> <span class="o">=</span> <span class="n">path</span><span class="o">.</span><span class="na">join</span><span class="o">(</span><span class="n">tempDir</span><span class="o">.</span><span class="na">path</span><span class="o">,</span> <span class="n">path</span><span class="o">.</span><span class="na">basename</span><span class="o">(</span><span class="n">image</span><span class="o">.</span><span class="na">path</span><span class="o">));</span>
<span class="n">print</span><span class="o">(</span><span class="n">tempPath</span><span class="o">);</span> <span class="c1">// /data/user/0/com.memi.ff6/cache/britney-spears-plastic-surgery.jpg</span>
</code></pre></div></div>
<p>선택한 이미지 파일의 임시 경로 위치를 만들었습니다.</p>
<h2 id="압축함수-만들기">압축함수 만들기</h2>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Future</span><span class="o"><</span><span class="n">File</span><span class="o">></span> <span class="n">_compressFile</span><span class="o">(</span><span class="n">File</span> <span class="n">file</span><span class="o">,</span> <span class="kt">String</span> <span class="n">tempPath</span><span class="o">)</span> <span class="n">async</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">result</span> <span class="o">=</span> <span class="n">await</span> <span class="n">FlutterImageCompress</span><span class="o">.</span><span class="na">compressAndGetFile</span><span class="o">(</span>
<span class="n">file</span><span class="o">.</span><span class="na">absolute</span><span class="o">.</span><span class="na">path</span><span class="o">,</span>
<span class="n">tempPath</span><span class="o">,</span>
<span class="nl">minWidth:</span> <span class="mi">200</span><span class="o">,</span>
<span class="nl">minHeight:</span> <span class="mi">200</span><span class="o">,</span>
<span class="nl">quality:</span> <span class="mi">90</span><span class="o">,</span>
<span class="c1">// rotate: 90,</span>
<span class="o">);</span>
<span class="k">return</span> <span class="n">result</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>해상도를 줄이고 압축한 결과물을 얻습니다.</p>
<h2 id="이미지-가져오기">이미지 가져오기</h2>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Future</span> <span class="nf">getImage</span><span class="p">(</span><span class="n">ImageSource</span> <span class="kn">source</span><span class="o">)</span> <span class="n">async</span> <span class="o">{</span>
<span class="n">File</span> <span class="n">image</span> <span class="o">=</span> <span class="n">await</span> <span class="n">ImagePicker</span><span class="o">.</span><span class="na">pickImage</span><span class="o">(</span><span class="kn">source</span><span class="o">:</span> <span class="kn">source</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">image</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="k">return</span><span class="o">;</span>
<span class="n">Directory</span> <span class="n">tempDir</span> <span class="o">=</span> <span class="n">await</span> <span class="n">getTemporaryDirectory</span><span class="o">();</span>
<span class="kt">String</span> <span class="n">tempPath</span> <span class="o">=</span> <span class="n">path</span><span class="o">.</span><span class="na">join</span><span class="o">(</span><span class="n">tempDir</span><span class="o">.</span><span class="na">path</span><span class="o">,</span> <span class="n">path</span><span class="o">.</span><span class="na">basename</span><span class="o">(</span><span class="n">image</span><span class="o">.</span><span class="na">path</span><span class="o">));</span>
<span class="n">File</span> <span class="n">tempImage</span> <span class="o">=</span> <span class="n">await</span> <span class="n">_compressFile</span><span class="o">(</span><span class="n">image</span><span class="o">,</span> <span class="n">tempPath</span><span class="o">);</span>
<span class="n">print</span><span class="o">(</span><span class="n">image</span><span class="o">.</span><span class="na">lengthSync</span><span class="o">());</span> <span class="c1">// 157083</span>
<span class="n">print</span><span class="o">(</span><span class="n">tempImage</span><span class="o">.</span><span class="na">lengthSync</span><span class="o">());</span> <span class="c1">// 21526</span>
<span class="n">setState</span><span class="o">(()</span> <span class="o">=></span> <span class="n">_image</span> <span class="o">=</span> <span class="n">tempImage</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>비약적으로 이미지 사이즈가 줄었습니다.</p>
<h1 id="테스트">테스트</h1>
<p>안드로이드의 경우 시뮬레이터 카메라로 가져올 수도 있습니다.</p>
<p><img src="/images/ff/2020-04-03_19.09.50.png" alt="alt camera" /></p>
<p><img src="/images/ff/2020-04-03_19.18.04.png" alt="alt cameraP" /></p>
<h1 id="소스">소스</h1>
<p><a href="https://github.com/fkkmemi/ff2">https://github.com/fkkmemi/ff2</a></p>
<h1 id="영상">영상</h1>
<!-- Courtesy of embedresponsively.com //-->
<div class="responsive-video-container">
<iframe src="https://www.youtube-nocookie.com/embed/b8MKLsea0Tk" frameborder="0" allowfullscreen=""></iframe>
</div>memifkkmemi@gmail.com플러터 image_picker 모듈을 사용해서 카메라의 사진 혹은 내장 앨범의 사진을 가져오고 압축해서 표시해봅니다.Flutter와 Firebase로 Android iOS 둘 다 만들기 22 프로필 페이지 꾸미기2020-04-01T00:00:00+00:002020-04-01T00:00:00+00:00https://fkkmemi.github.io/ff/ff%20022<p>플러터 레이아웃을 이용해 프로필 페이지를 간단히 꾸며봅니다.</p>
<h1 id="개요">개요</h1>
<p>플러터로 앱을 제작할 때 가장 힘든 것은 역시 레이아웃입니다.</p>
<p>이유는 너무나도 많은 방법으로 같은 결과물을 얻을 수 있기 때문입니다.</p>
<p>많은 방법이 득이 될 때보다 실이 될 수 있는 좋은 예인 것 같습니다.</p>
<p>잘 할 수 있는 방법은 여러가지 방법으로 많은 실험을 해보고 최대한 코드를 줄여가며 익숙해지는 방법 밖에는 없는 것 같습니다.</p>
<h1 id="프로필페이지">프로필페이지</h1>
<h2 id="프로필페이지-라우터-생성">프로필페이지 라우터 생성</h2>
<p><strong>main.dart</strong></p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">case</span> <span class="n">ProfilePage</span><span class="o">.</span><span class="na">routeName</span><span class="o">:</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">MaterialPageRoute</span><span class="o">(</span>
<span class="nl">builder:</span> <span class="o">(</span><span class="n">context</span><span class="o">)</span> <span class="o">=></span> <span class="n">ProfilePage</span><span class="o">(</span><span class="nl">user:</span> <span class="n">settings</span><span class="o">.</span><span class="na">arguments</span><span class="o">)</span>
<span class="o">);</span>
<span class="o">}</span> <span class="k">break</span><span class="o">;</span>
</code></pre></div></div>
<p>프로필페이지에도 user정보를 넘겨줍니다.</p>
<h2 id="프로필페이지-버튼-꾸미기">프로필페이지 버튼 꾸미기</h2>
<p><strong>pages/home.dart</strong></p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Widget</span> <span class="nf">_buildProfile</span><span class="p">(</span><span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Padding</span><span class="o">(</span>
<span class="nl">padding:</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="o">(</span><span class="mi">8</span><span class="o">),</span>
<span class="nl">child:</span> <span class="n">InkWell</span><span class="o">(</span>
<span class="nl">child:</span> <span class="n">CircleAvatar</span><span class="o">(</span>
<span class="nl">backgroundImage:</span> <span class="n">NetworkImage</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">photoUrl</span><span class="o">),</span>
<span class="o">),</span>
<span class="nl">onTap:</span> <span class="o">()</span> <span class="o">{</span>
<span class="n">Navigator</span><span class="o">.</span><span class="na">pushNamed</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="s">'/profile'</span><span class="o">,</span> <span class="nl">arguments:</span> <span class="n">user</span><span class="o">);</span>
<span class="o">},</span>
<span class="o">),</span>
<span class="o">);</span>
<span class="o">}</span>
<span class="nd">@override</span>
<span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Scaffold</span><span class="o">(</span>
<span class="nl">appBar:</span> <span class="n">AppBar</span><span class="o">(</span>
<span class="nl">title:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'HomePage'</span><span class="o">),</span>
<span class="nl">actions:</span> <span class="o"><</span><span class="n">Widget</span><span class="o">>[</span>
<span class="n">_buildProfile</span><span class="o">(</span><span class="n">context</span><span class="o">),</span>
<span class="o">],</span>
<span class="o">),</span>
<span class="c1">// ..</span>
<span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p><img src="/images/ff/2020-04-01_14.10.17.png" alt="alt profilebutton" /></p>
<p>이제 상단 우측의 프로필버튼을 클릭하면 프로필페이지로 이동합니다.</p>
<h2 id="프로필페이지-꾸미기">프로필페이지 꾸미기</h2>
<p><strong>pages/profile.dart</strong></p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>
<span class="kn">import</span> <span class="s">'package:firebase_auth/firebase_auth.dart'</span><span class="o">;</span>
<span class="kd">class</span> <span class="nc">ProfilePage</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span>
<span class="kd">const</span> <span class="n">ProfilePage</span><span class="o">({</span><span class="n">Key</span> <span class="n">key</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">user</span><span class="o">})</span> <span class="o">:</span> <span class="k">super</span><span class="o">(</span><span class="nl">key:</span> <span class="n">key</span><span class="o">);</span>
<span class="kd">static</span> <span class="kd">const</span> <span class="n">routeName</span> <span class="o">=</span> <span class="s">'/profile'</span><span class="o">;</span>
<span class="kd">final</span> <span class="n">FirebaseUser</span> <span class="n">user</span><span class="o">;</span>
<span class="n">Widget</span> <span class="n">_buildCard</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Card</span><span class="o">(</span>
<span class="nl">child:</span> <span class="n">ListTile</span><span class="o">(</span>
<span class="nl">leading:</span> <span class="n">Image</span><span class="o">.</span><span class="na">network</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">photoUrl</span><span class="o">),</span>
<span class="nl">title:</span> <span class="n">Text</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">displayName</span><span class="o">),</span>
<span class="nl">subtitle:</span> <span class="n">Text</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">email</span><span class="o">),</span>
<span class="nl">trailing:</span> <span class="n">IconButton</span><span class="o">(</span>
<span class="nl">icon:</span> <span class="n">Icon</span><span class="o">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">settings</span><span class="o">),</span>
<span class="nl">onPressed:</span> <span class="o">()</span> <span class="o">{</span>
<span class="n">Navigator</span><span class="o">.</span><span class="na">pushNamed</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="s">'/profile-edit'</span><span class="o">,</span> <span class="nl">arguments:</span> <span class="n">user</span><span class="o">);</span>
<span class="o">},</span>
<span class="o">),</span>
<span class="o">),</span>
<span class="o">);</span>
<span class="o">}</span>
<span class="n">Widget</span> <span class="n">_buildSignOut</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">ButtonBar</span><span class="o">(</span>
<span class="nl">alignment:</span> <span class="n">MainAxisAlignment</span><span class="o">.</span><span class="na">center</span><span class="o">,</span>
<span class="nl">children:</span> <span class="o"><</span><span class="n">Widget</span><span class="o">>[</span>
<span class="n">RaisedButton</span><span class="o">(</span>
<span class="nl">child:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'Sign out'</span><span class="o">),</span>
<span class="nl">onPressed:</span> <span class="o">()</span> <span class="n">async</span> <span class="o">{</span>
<span class="n">await</span> <span class="n">FirebaseAuth</span><span class="o">.</span><span class="na">instance</span><span class="o">.</span><span class="na">signOut</span><span class="o">();</span>
<span class="n">Navigator</span><span class="o">.</span><span class="na">pushNamedAndRemoveUntil</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="s">'/auth'</span><span class="o">,</span> <span class="o">(</span><span class="n">route</span><span class="o">)</span> <span class="o">=></span> <span class="kc">false</span><span class="o">);</span>
<span class="c1">// Navigator.pushReplacementNamed(context, '/auth');</span>
<span class="o">},</span>
<span class="o">)</span>
<span class="o">],</span>
<span class="o">);</span>
<span class="o">}</span>
<span class="n">Widget</span> <span class="n">_buildBody</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">SingleChildScrollView</span><span class="o">(</span>
<span class="nl">child:</span> <span class="n">Container</span><span class="o">(</span>
<span class="nl">padding:</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="o">(</span><span class="mi">10</span><span class="o">),</span>
<span class="nl">child:</span> <span class="n">Column</span><span class="o">(</span>
<span class="nl">mainAxisAlignment:</span> <span class="n">MainAxisAlignment</span><span class="o">.</span><span class="na">spaceAround</span><span class="o">,</span>
<span class="nl">crossAxisAlignment:</span> <span class="n">CrossAxisAlignment</span><span class="o">.</span><span class="na">center</span><span class="o">,</span>
<span class="nl">children:</span> <span class="o"><</span><span class="n">Widget</span><span class="o">>[</span>
<span class="n">_buildCard</span><span class="o">(</span><span class="n">context</span><span class="o">),</span>
<span class="n">_buildSignOut</span><span class="o">(</span><span class="n">context</span><span class="o">),</span>
<span class="o">],</span>
<span class="o">),</span>
<span class="o">),</span>
<span class="o">);</span>
<span class="o">}</span>
<span class="nd">@override</span>
<span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Scaffold</span><span class="o">(</span>
<span class="nl">appBar:</span> <span class="n">AppBar</span><span class="o">(</span><span class="nl">title:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'Profile'</span><span class="o">),),</span>
<span class="nl">body:</span> <span class="n">_buildBody</span><span class="o">(</span><span class="n">context</span><span class="o">),</span>
<span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p><img src="/images/ff/2020-04-01_15.43.07.png" alt="alt profile" /></p>
<p>카드와 리스트 타일을 사용해서 간단히 구색만 맞춰봤습니다.</p>
<p>stateful과는 다르게 stateless 위젯은 context를 하위로 넘겨줘야합니다.</p>
<blockquote>
<p>현재 페이지에서 수정을 만들 수도 있지만.. 지저분해 질 것 같아서 수정은 다른 페이지에서 처리하도록 합니다.</p>
</blockquote>
<p>세팅버튼을 눌러서 profile-edit 페이지로 이동시킵니다.</p>
<h1 id="프로필수정">프로필수정</h1>
<h2 id="라우터-생성자에-추가">라우터 생성자에 추가</h2>
<p>위처럼 프로필수정페이지를 만들고 라우터 생성자에 넣어줍니다.</p>
<p><strong>main.dart</strong></p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">case</span> <span class="n">ProfileEditPage</span><span class="o">.</span><span class="na">routeName</span><span class="o">:</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">MaterialPageRoute</span><span class="o">(</span>
<span class="nl">builder:</span> <span class="o">(</span><span class="n">context</span><span class="o">)</span> <span class="o">=></span> <span class="n">ProfileEditPage</span><span class="o">(</span><span class="nl">user:</span> <span class="n">settings</span><span class="o">.</span><span class="na">arguments</span><span class="o">)</span>
<span class="o">);</span>
<span class="o">}</span> <span class="k">break</span><span class="o">;</span>
</code></pre></div></div>
<h2 id="다트-데브툴-이용하기">다트 데브툴 이용하기</h2>
<p>레이아웃이 잘 이해가 가지 않을 때는..</p>
<p>보기 -> 명령팔레트를 누르고 Dart: Open devtools를 눌러서 레이아웃을 확인하는 것도 방법입니다.</p>
<p><img src="/images/ff/2020-04-01_14.38.05.png" alt="alt command" /></p>
<p><img src="/images/ff/2020-04-01_14.40.18.png" alt="alt dartdev" /></p>
<p>위처럼 Debug paint, Paint Baselines등으로 바로 확인이 가능합니다.</p>
<h2 id="프로필-수정-페이지-초안">프로필 수정 페이지 초안</h2>
<p><strong>pages/profile_edit.dart</strong></p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:firebase_auth/firebase_auth.dart'</span><span class="o">;</span>
<span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>
<span class="kd">class</span> <span class="nc">ProfileEditPage</span> <span class="kd">extends</span> <span class="n">StatefulWidget</span> <span class="o">{</span>
<span class="n">ProfileEditPage</span><span class="o">({</span><span class="n">Key</span> <span class="n">key</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">user</span><span class="o">})</span> <span class="o">:</span> <span class="k">super</span><span class="o">(</span><span class="nl">key:</span> <span class="n">key</span><span class="o">);</span>
<span class="kd">static</span> <span class="kd">const</span> <span class="n">routeName</span> <span class="o">=</span> <span class="s">'/profile-edit'</span><span class="o">;</span>
<span class="kd">final</span> <span class="n">FirebaseUser</span> <span class="n">user</span><span class="o">;</span>
<span class="nd">@override</span>
<span class="n">_ProfileEditPageState</span> <span class="n">createState</span><span class="o">()</span> <span class="o">=></span> <span class="n">_ProfileEditPageState</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">class</span> <span class="nc">_ProfileEditPageState</span> <span class="kd">extends</span> <span class="n">State</span><span class="o"><</span><span class="n">ProfileEditPage</span><span class="o">></span> <span class="o">{</span>
<span class="n">Widget</span> <span class="n">_buildBody</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">SingleChildScrollView</span><span class="o">(</span>
<span class="nl">child:</span> <span class="n">Container</span><span class="o">(</span>
<span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">redAccent</span><span class="o">,</span>
<span class="nl">height:</span> <span class="mi">200</span><span class="o">,</span>
<span class="nl">padding:</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="o">(</span><span class="mi">10</span><span class="o">),</span>
<span class="o">),</span>
<span class="o">);</span>
<span class="o">}</span>
<span class="nd">@override</span>
<span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Scaffold</span><span class="o">(</span>
<span class="nl">appBar:</span> <span class="n">AppBar</span><span class="o">(</span>
<span class="nl">title:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'Profile Edit'</span><span class="o">),</span>
<span class="nl">actions:</span> <span class="o"><</span><span class="n">Widget</span><span class="o">>[</span>
<span class="n">IconButton</span><span class="o">(</span>
<span class="nl">icon:</span> <span class="n">Icon</span><span class="o">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">save</span><span class="o">),</span>
<span class="nl">onPressed:</span> <span class="o">()</span> <span class="o">{},</span>
<span class="o">),</span>
<span class="o">],</span>
<span class="o">),</span>
<span class="nl">body:</span> <span class="n">_buildBody</span><span class="o">(),</span>
<span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>컨테이너에는 크기와 색상을 넣어 둡니다.</p>
<p>이렇게 해야 레이아웃이 눈에 보여서 구성하기가 좋습니다.</p>
<p>여기에서 제일 중요한 것은 height 200입니다.</p>
<p>200 높이를 지정하고 그 안에서 자식들이 위치를 잡게 합니다.</p>
<h2 id="사진-이름-자리-잡기">사진, 이름 자리 잡기</h2>
<p><strong>pages/profile_edit.dart</strong></p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Widget</span> <span class="nf">_buildBody</span><span class="p">(</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">SingleChildScrollView</span><span class="o">(</span>
<span class="nl">child:</span> <span class="n">Container</span><span class="o">(</span>
<span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">redAccent</span><span class="o">,</span>
<span class="nl">height:</span> <span class="mi">200</span><span class="o">,</span>
<span class="nl">padding:</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="o">(</span><span class="mi">10</span><span class="o">),</span>
<span class="nl">child:</span> <span class="n">Card</span><span class="o">(</span>
<span class="nl">child:</span> <span class="n">Row</span><span class="o">(</span>
<span class="nl">mainAxisAlignment:</span> <span class="n">MainAxisAlignment</span><span class="o">.</span><span class="na">spaceBetween</span><span class="o">,</span>
<span class="nl">children:</span> <span class="o"><</span><span class="n">Widget</span><span class="o">>[</span>
<span class="n">Expanded</span><span class="o">(</span>
<span class="nl">flex:</span> <span class="mi">2</span><span class="o">,</span>
<span class="nl">child:</span> <span class="n">Container</span><span class="o">(</span><span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">blueAccent</span><span class="o">,),</span>
<span class="o">),</span>
<span class="n">Expanded</span><span class="o">(</span>
<span class="nl">flex:</span> <span class="mi">3</span><span class="o">,</span>
<span class="nl">child:</span> <span class="n">Container</span><span class="o">(</span><span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">brown</span><span class="o">,),</span>
<span class="o">),</span>
<span class="o">],</span>
<span class="o">),</span>
<span class="o">),</span>
<span class="o">),</span>
<span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Row로 사진과 이름이 들어갈 자리를 만들어둡니다.</p>
<p>Expanded의 flex로 2/3 비율로 공간이 할당이 됩니다.</p>
<p><img src="/images/ff/2020-04-01_15.00.54.png" alt="alt flex" /></p>
<blockquote>
<p>flex의 경우 웹프레임워크 대부분이 10, 12, 16등을 나눠서 쓰는데(3 + 3 + 6 = 12) 플러터의 경우 특이하게 비율로 되어있어서 더 나은 것 같습니다.</p>
</blockquote>
<h2 id="사진-꾸미기-초안">사진 꾸미기 초안</h2>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Widget</span> <span class="nf">_buildPhoto</span><span class="p">(</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Container</span><span class="o">(</span>
<span class="nl">padding:</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="o">(</span><span class="mi">10</span><span class="o">),</span>
<span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">teal</span><span class="o">,</span>
<span class="nl">child:</span> <span class="n">Stack</span><span class="o">(</span>
<span class="nl">alignment:</span> <span class="n">Alignment</span><span class="o">.</span><span class="na">bottomCenter</span><span class="o">,</span>
<span class="nl">children:</span> <span class="o"><</span><span class="n">Widget</span><span class="o">>[</span>
<span class="n">Container</span><span class="o">(</span>
<span class="nl">child:</span> <span class="n">Image</span><span class="o">.</span><span class="na">network</span><span class="o">(</span><span class="n">widget</span><span class="o">.</span><span class="na">user</span><span class="o">.</span><span class="na">photoUrl</span><span class="o">),</span>
<span class="o">),</span>
<span class="n">Text</span><span class="o">(</span><span class="s">'hi yo!'</span><span class="o">),</span>
<span class="o">],</span>
<span class="o">),</span>
<span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p><img src="/images/ff/2020-04-01_15.13.17.png" alt="alt photo1" /></p>
<p>Stack이라는 위젯은 자식들끼리 겹치게 할 수 있는 위젯입니다.</p>
<p>제일 중요한 옵션이 alignment인데요 바닥 가운데로 지정하게 하여 글씨와 사진이 겹치게 구성되어 있습니다.</p>
<p>그런데 사진 사이즈가 작아서 원하는데로 꽉차지가 않습니다.</p>
<h2 id="사진-꾸미기-마무리">사진 꾸미기 마무리</h2>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Widget</span> <span class="nf">_buildPhoto</span><span class="p">(</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Container</span><span class="o">(</span>
<span class="nl">padding:</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="o">(</span><span class="mi">10</span><span class="o">),</span>
<span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">teal</span><span class="o">,</span>
<span class="nl">child:</span> <span class="n">Stack</span><span class="o">(</span>
<span class="nl">alignment:</span> <span class="n">Alignment</span><span class="o">.</span><span class="na">bottomCenter</span><span class="o">,</span>
<span class="nl">children:</span> <span class="o"><</span><span class="n">Widget</span><span class="o">>[</span>
<span class="n">Container</span><span class="o">(</span>
<span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">teal</span><span class="o">,</span>
<span class="nl">constraints:</span> <span class="n">BoxConstraints</span><span class="o">.</span><span class="na">expand</span><span class="o">(),</span>
<span class="nl">child:</span> <span class="n">Image</span><span class="o">.</span><span class="na">network</span><span class="o">(</span><span class="n">widget</span><span class="o">.</span><span class="na">user</span><span class="o">.</span><span class="na">photoUrl</span><span class="o">,</span> <span class="nl">fit:</span> <span class="n">BoxFit</span><span class="o">.</span><span class="na">cover</span><span class="o">),</span>
<span class="o">),</span>
<span class="n">Container</span><span class="o">(</span>
<span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">black54</span><span class="o">,</span>
<span class="nl">height:</span> <span class="mi">40</span><span class="o">,</span>
<span class="o">),</span>
<span class="n">ButtonBar</span><span class="o">(</span>
<span class="nl">alignment:</span> <span class="n">MainAxisAlignment</span><span class="o">.</span><span class="na">end</span><span class="o">,</span>
<span class="nl">children:</span> <span class="o"><</span><span class="n">Widget</span><span class="o">>[</span>
<span class="n">Icon</span><span class="o">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">photo_camera</span><span class="o">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">white</span><span class="o">,</span> <span class="o">),</span>
<span class="n">Icon</span><span class="o">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">photo_library</span><span class="o">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">white</span><span class="o">,</span> <span class="o">),</span>
<span class="o">],</span>
<span class="o">)</span>
<span class="o">],</span>
<span class="o">),</span>
<span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p><img src="/images/ff/2020-04-01_15.15.52.png" alt="alt photo2" /></p>
<p>constraints으로 확장시키고 Image의 fit으로 조정해서 남은 사이즈에 꽉차게 했습니다.</p>
<p>버튼바는 좌우 100% 확장 성질이 있습니다.</p>
<p>반투명한 컨테이너를 버튼바에 겹치게 만들어 봤습니다.</p>
<h2 id="이름-꾸미기">이름 꾸미기</h2>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Widget</span> <span class="nf">_buildName</span><span class="p">(</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Container</span><span class="o">(</span>
<span class="nl">padding:</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="o">(</span><span class="mi">10</span><span class="o">),</span>
<span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">brown</span><span class="o">,</span>
<span class="nl">child:</span> <span class="n">Column</span><span class="o">(</span>
<span class="nl">mainAxisAlignment:</span> <span class="n">MainAxisAlignment</span><span class="o">.</span><span class="na">spaceEvenly</span><span class="o">,</span>
<span class="nl">children:</span> <span class="o"><</span><span class="n">Widget</span><span class="o">>[</span>
<span class="n">TextFormField</span><span class="o">(</span>
<span class="nl">decoration:</span> <span class="n">InputDecoration</span><span class="o">(</span>
<span class="nl">labelText:</span> <span class="s">'First name*'</span><span class="o">,</span>
<span class="nl">hintText:</span> <span class="s">'John'</span><span class="o">,</span>
<span class="nl">border:</span> <span class="n">OutlineInputBorder</span><span class="o">(),</span>
<span class="o">),</span>
<span class="o">),</span>
<span class="n">TextFormField</span><span class="o">(</span>
<span class="nl">decoration:</span> <span class="n">InputDecoration</span><span class="o">(</span>
<span class="nl">labelText:</span> <span class="s">'Last name*'</span><span class="o">,</span>
<span class="nl">hintText:</span> <span class="s">'Doe'</span><span class="o">,</span>
<span class="nl">border:</span> <span class="n">OutlineInputBorder</span><span class="o">(),</span>
<span class="o">),</span>
<span class="o">),</span>
<span class="o">],</span>
<span class="o">),</span>
<span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p><img src="/images/ff/2020-04-01_15.22.27.png" alt="alt name" /></p>
<p>사진처럼 패딩을 10 줘서 균형을 잡고.. Column으로 남은 공간을 적당하게 분배해서 입력폼을 넣습니다.</p>
<h1 id="마무리">마무리</h1>
<p>레이아웃을 보기위해 지정했던 색상을 모두 지웁니다.</p>
<p><strong>pages/profile_edit.dart</strong></p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:firebase_auth/firebase_auth.dart'</span><span class="o">;</span>
<span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>
<span class="kd">class</span> <span class="nc">ProfileEditPage</span> <span class="kd">extends</span> <span class="n">StatefulWidget</span> <span class="o">{</span>
<span class="n">ProfileEditPage</span><span class="o">({</span><span class="n">Key</span> <span class="n">key</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">user</span><span class="o">})</span> <span class="o">:</span> <span class="k">super</span><span class="o">(</span><span class="nl">key:</span> <span class="n">key</span><span class="o">);</span>
<span class="kd">static</span> <span class="kd">const</span> <span class="n">routeName</span> <span class="o">=</span> <span class="s">'/profile-edit'</span><span class="o">;</span>
<span class="kd">final</span> <span class="n">FirebaseUser</span> <span class="n">user</span><span class="o">;</span>
<span class="nd">@override</span>
<span class="n">_ProfileEditPageState</span> <span class="n">createState</span><span class="o">()</span> <span class="o">=></span> <span class="n">_ProfileEditPageState</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">class</span> <span class="nc">_ProfileEditPageState</span> <span class="kd">extends</span> <span class="n">State</span><span class="o"><</span><span class="n">ProfileEditPage</span><span class="o">></span> <span class="o">{</span>
<span class="n">Widget</span> <span class="n">_buildPhoto</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Container</span><span class="o">(</span>
<span class="nl">padding:</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="o">(</span><span class="mi">10</span><span class="o">),</span>
<span class="nl">child:</span> <span class="n">Stack</span><span class="o">(</span>
<span class="nl">alignment:</span> <span class="n">Alignment</span><span class="o">.</span><span class="na">bottomCenter</span><span class="o">,</span>
<span class="nl">children:</span> <span class="o"><</span><span class="n">Widget</span><span class="o">>[</span>
<span class="n">Container</span><span class="o">(</span>
<span class="nl">constraints:</span> <span class="n">BoxConstraints</span><span class="o">.</span><span class="na">expand</span><span class="o">(),</span>
<span class="nl">child:</span> <span class="n">Image</span><span class="o">.</span><span class="na">network</span><span class="o">(</span><span class="n">widget</span><span class="o">.</span><span class="na">user</span><span class="o">.</span><span class="na">photoUrl</span><span class="o">,</span> <span class="nl">fit:</span> <span class="n">BoxFit</span><span class="o">.</span><span class="na">cover</span><span class="o">),</span>
<span class="o">),</span>
<span class="n">Container</span><span class="o">(</span>
<span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">black54</span><span class="o">,</span>
<span class="nl">height:</span> <span class="mi">40</span><span class="o">,</span>
<span class="o">),</span>
<span class="n">ButtonBar</span><span class="o">(</span>
<span class="nl">alignment:</span> <span class="n">MainAxisAlignment</span><span class="o">.</span><span class="na">end</span><span class="o">,</span>
<span class="nl">children:</span> <span class="o"><</span><span class="n">Widget</span><span class="o">>[</span>
<span class="n">Icon</span><span class="o">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">photo_camera</span><span class="o">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">white</span><span class="o">,</span> <span class="o">),</span>
<span class="n">Icon</span><span class="o">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">photo_library</span><span class="o">,</span> <span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">white</span><span class="o">,</span> <span class="o">),</span>
<span class="o">],</span>
<span class="o">)</span>
<span class="o">],</span>
<span class="o">),</span>
<span class="o">);</span>
<span class="o">}</span>
<span class="n">Widget</span> <span class="n">_buildName</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Container</span><span class="o">(</span>
<span class="nl">padding:</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="o">(</span><span class="mi">10</span><span class="o">),</span>
<span class="nl">child:</span> <span class="n">Column</span><span class="o">(</span>
<span class="nl">mainAxisAlignment:</span> <span class="n">MainAxisAlignment</span><span class="o">.</span><span class="na">spaceEvenly</span><span class="o">,</span>
<span class="nl">children:</span> <span class="o"><</span><span class="n">Widget</span><span class="o">>[</span>
<span class="n">TextFormField</span><span class="o">(</span>
<span class="nl">decoration:</span> <span class="n">InputDecoration</span><span class="o">(</span>
<span class="nl">labelText:</span> <span class="s">'First name*'</span><span class="o">,</span>
<span class="nl">hintText:</span> <span class="s">'John'</span><span class="o">,</span>
<span class="nl">border:</span> <span class="n">OutlineInputBorder</span><span class="o">(),</span>
<span class="o">),</span>
<span class="o">),</span>
<span class="n">TextFormField</span><span class="o">(</span>
<span class="nl">decoration:</span> <span class="n">InputDecoration</span><span class="o">(</span>
<span class="nl">labelText:</span> <span class="s">'Last name*'</span><span class="o">,</span>
<span class="nl">hintText:</span> <span class="s">'Doe'</span><span class="o">,</span>
<span class="nl">border:</span> <span class="n">OutlineInputBorder</span><span class="o">(),</span>
<span class="o">),</span>
<span class="o">),</span>
<span class="o">],</span>
<span class="o">),</span>
<span class="o">);</span>
<span class="o">}</span>
<span class="n">Widget</span> <span class="n">_buildBody</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">SingleChildScrollView</span><span class="o">(</span>
<span class="nl">child:</span> <span class="n">Container</span><span class="o">(</span>
<span class="nl">height:</span> <span class="mi">200</span><span class="o">,</span>
<span class="nl">padding:</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="o">(</span><span class="mi">10</span><span class="o">),</span>
<span class="nl">child:</span> <span class="n">Card</span><span class="o">(</span>
<span class="nl">child:</span> <span class="n">Row</span><span class="o">(</span>
<span class="nl">mainAxisAlignment:</span> <span class="n">MainAxisAlignment</span><span class="o">.</span><span class="na">spaceBetween</span><span class="o">,</span>
<span class="nl">children:</span> <span class="o"><</span><span class="n">Widget</span><span class="o">>[</span>
<span class="n">Expanded</span><span class="o">(</span>
<span class="nl">flex:</span> <span class="mi">2</span><span class="o">,</span>
<span class="nl">child:</span> <span class="n">_buildPhoto</span><span class="o">(),</span>
<span class="o">),</span>
<span class="n">Expanded</span><span class="o">(</span>
<span class="nl">flex:</span> <span class="mi">3</span><span class="o">,</span>
<span class="nl">child:</span> <span class="n">_buildName</span><span class="o">(),</span>
<span class="o">),</span>
<span class="o">],</span>
<span class="o">),</span>
<span class="o">),</span>
<span class="o">),</span>
<span class="o">);</span>
<span class="o">}</span>
<span class="nd">@override</span>
<span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Scaffold</span><span class="o">(</span>
<span class="nl">appBar:</span> <span class="n">AppBar</span><span class="o">(</span>
<span class="nl">title:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'Profile Edit'</span><span class="o">),</span>
<span class="nl">actions:</span> <span class="o"><</span><span class="n">Widget</span><span class="o">>[</span>
<span class="n">IconButton</span><span class="o">(</span>
<span class="nl">icon:</span> <span class="n">Icon</span><span class="o">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">save</span><span class="o">),</span>
<span class="nl">onPressed:</span> <span class="o">()</span> <span class="o">{},</span>
<span class="o">),</span>
<span class="o">],</span>
<span class="o">),</span>
<span class="nl">body:</span> <span class="n">_buildBody</span><span class="o">(),</span>
<span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p><strong>완성된 모습</strong><br />
<img src="/images/ff/2020-04-01_15.27.11.png" alt="alt fin" /></p>
<h1 id="마치며">마치며</h1>
<p>이렇게 간단한 화면을 구성하는데도 생각처럼 잘 되지 않습니다.</p>
<p>특정 위젯이 사이즈가 없어서 표현이 안되거나 에러가 나는 경우가 부지기수입니다.</p>
<p>급하게 화면을 만드려고 억지로 코드를 우겨넣어서 화면을 만들다보면 나중에 더 힘들어집니다.</p>
<p>컨테이너와 공간에 대한 생각을 끊임 없이 해보고 직접 테스트 해봐야 조금 감이오기 시작합니다.</p>
<p>레이아웃에 대해 제가 너무 잘해서 강의를 진행하는 것이 아닌, 제가 잘 못하기 때문에 좀 더 보강하는 것입니다.</p>
<h1 id="소스">소스</h1>
<p><a href="https://github.com/fkkmemi/ff2">https://github.com/fkkmemi/ff2</a></p>
<h1 id="영상">영상</h1>
<!-- Courtesy of embedresponsively.com //-->
<div class="responsive-video-container">
<iframe src="https://www.youtube-nocookie.com/embed/xJtQCeb21Ow" frameborder="0" allowfullscreen=""></iframe>
</div>memifkkmemi@gmail.com플러터 레이아웃을 이용해 프로필 페이지를 간단히 꾸며봅니다.Flutter와 Firebase로 Android iOS 둘 다 만들기 21 페이지에 데이터 넘기기2020-03-30T00:00:00+00:002020-03-30T00:00:00+00:00https://fkkmemi.github.io/ff/ff%20021<p>새 페이지로 라우팅할 때 데이터를 넘겨봅니다.</p>
<h1 id="개요">개요</h1>
<p>로그인 정보를 한번 취득한 후, 다른 페이지에서 로그인 정보를 열람해야할 때 또 정보를 얻어내는 것은 낭비입니다.</p>
<p>라우팅 할 때 아규먼트를 넣어서 페이지를 생성할 수 있습니다.</p>
<p>다양한 방법이 있으나 현재 필요한 것은 3가지 입니다.</p>
<ul>
<li>인자가 있는 라우터 등록</li>
<li>페이지에 받을 인자 추가</li>
<li>페이지 호출시 인자 추가</li>
</ul>
<p>페이지 전환 시 데이터를 넘겨서 프로필 이미지를 완성해봅니다.</p>
<h1 id="라우터-생성기">라우터 생성기</h1>
<p>먼저 공식홈의 예제를 한번 읽어보고 오시기 바랍니다.</p>
<p>참고: <a href="https://flutter-ko.dev/docs/cookbook/navigation/navigate-with-arguments">https://flutter-ko.dev/docs/cookbook/navigation/navigate-with-arguments</a></p>
<p>사용자 정보를 넘기기 위해 라우터를 변경합니다.</p>
<p><strong>main.dart</strong></p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">routes:</span> <span class="o">{</span>
<span class="c1">// ..</span>
<span class="c1">// HomePage.routeName: (context) => HomePage(), </span>
<span class="o">},</span>
<span class="nl">onGenerateRoute:</span> <span class="o">(</span><span class="n">settings</span><span class="o">)</span> <span class="o">{</span>
<span class="k">switch</span> <span class="o">(</span><span class="n">settings</span><span class="o">.</span><span class="na">name</span><span class="o">)</span> <span class="o">{</span>
<span class="k">case</span> <span class="n">HomePage</span><span class="o">.</span><span class="na">routeName</span><span class="o">:</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">MaterialPageRoute</span><span class="o">(</span>
<span class="nl">builder:</span> <span class="o">(</span><span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">HomePage</span><span class="o">(</span><span class="nl">user:</span> <span class="n">settings</span><span class="o">.</span><span class="na">arguments</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">);</span>
<span class="o">}</span> <span class="k">break</span><span class="o">;</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">MaterialPageRoute</span><span class="o">(</span>
<span class="nl">builder:</span> <span class="o">(</span><span class="n">context</span><span class="o">)</span> <span class="o">=></span> <span class="n">SplashPage</span><span class="o">()</span>
<span class="o">);</span>
<span class="o">},</span>
</code></pre></div></div>
<p>기존의 비워진 HomePage() 대신 HomePage(user: settings.arguments)가 대신하게 됩니다.</p>
<h1 id="페이지-호출시">페이지 호출시</h1>
<p><strong>pages/auth.dart</strong></p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// fu = firebase user</span>
<span class="n">Navigator</span><span class="o">.</span><span class="na">pushReplacementNamed</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="s">'/home'</span><span class="o">,</span> <span class="nl">arguments:</span> <span class="n">fu</span><span class="o">);</span>
</code></pre></div></div>
<p>파이어베이스 사용자 정보를 넘깁니다.</p>
<h1 id="클래스-생성자-추가">클래스 생성자 추가</h1>
<p><strong>pages/home.dart</strong></p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nf">HomePage</span><span class="p">(</span><span class="o">{</span><span class="n">Key</span> <span class="n">key</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">user</span><span class="o">})</span> <span class="o">:</span> <span class="k">super</span><span class="o">(</span><span class="nl">key:</span> <span class="n">key</span><span class="o">);</span>
<span class="kd">static</span> <span class="kd">const</span> <span class="n">routeName</span> <span class="o">=</span> <span class="s">'/home'</span><span class="o">;</span>
<span class="kd">final</span> <span class="n">user</span><span class="o">;</span>
<span class="c1">// ..</span>
<span class="k">return</span> <span class="nf">Scaffold</span><span class="p">(</span>
<span class="nl">appBar:</span> <span class="n">AppBar</span><span class="o">(</span>
<span class="c1">// title: Text('HomePage'),</span>
<span class="nl">title:</span> <span class="n">Text</span><span class="o">(</span><span class="n">user</span><span class="o">.</span><span class="na">email</span><span class="o">),</span>
<span class="c1">// ..</span>
</code></pre></div></div>
<p>이렇게 해야 받을 수 있습니다.</p>
<p>그리고 이메일을 출력해봅니다.</p>
<h1 id="소스">소스</h1>
<p><a href="https://github.com/fkkmemi/ff2">https://github.com/fkkmemi/ff2</a></p>
<h1 id="영상">영상</h1>
<!-- Courtesy of embedresponsively.com //-->
<div class="responsive-video-container">
<iframe src="https://www.youtube-nocookie.com/embed/Uf_gCKdVO78" frameborder="0" allowfullscreen=""></iframe>
</div>memifkkmemi@gmail.com새 페이지로 라우팅할 때 데이터를 넘겨봅니다.Flutter와 Firebase로 Android iOS 둘 다 만들기 20 회원가입 적용하기2020-03-27T00:00:00+00:002020-03-27T00:00:00+00:00https://fkkmemi.github.io/ff/ff%20020<p>회원가입 페이지를 만들고 이메일로 회원가입을 해봅니다.</p>
<h1 id="개요">개요</h1>
<p>단순 회원가입은 로그인 만큼 쉽습니다.</p>
<p>메써드만 바꿔주면 그만이기 때문입니다.</p>
<p>하지만 아무나 회원가입 시킬 경우 가입봇의 희생양이 될 수 있습니다.</p>
<p>회원가입 폼을 검사하고 이메일 유효성 체크 까지 할 수 있도록 만들어 보겠습니다.</p>
<h1 id="파이어베이스-세팅">파이어베이스 세팅</h1>
<p>먼저 이메일 인증을 사용하려면 로그인 방법을 추가해야합니다.</p>
<p><img src="/images/ff/2020-03-27_19.51.55.png" alt="alt firebase" /></p>
<h1 id="회원가입-폼-추가">회원가입 폼 추가</h1>
<p>지난번 로그인에서 패스워드와 이름 넣는 정도만 추가했습니다.</p>
<p><img src="/images/ff/2020-03-27_20.56.51.png" alt="alt signup" /></p>
<blockquote>
<p>화면 디자인에 대한 설명은 지난 번에 설명했으므로 생략합니다.</p>
</blockquote>
<h1 id="이메일-로그인-버튼">이메일 로그인 버튼</h1>
<p><strong>signup.dart</strong></p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">SignInButtonBuilder</span><span class="o">(</span>
<span class="nl">text:</span> <span class="s">'Sign up with Email'</span><span class="o">,</span>
<span class="nl">icon:</span> <span class="n">Icons</span><span class="o">.</span><span class="na">email</span><span class="o">,</span>
<span class="nl">backgroundColor:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">blueGrey</span><span class="o">[</span><span class="mi">700</span><span class="o">],</span>
<span class="nl">onPressed:</span> <span class="o">()</span> <span class="n">async</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">_formKey</span><span class="o">.</span><span class="na">currentState</span><span class="o">.</span><span class="na">validate</span><span class="o">())</span> <span class="k">return</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">passwordInputController</span><span class="o">.</span><span class="na">text</span> <span class="o">!=</span> <span class="n">confirmPasswordInputController</span><span class="o">.</span><span class="na">text</span><span class="o">)</span> <span class="o">{</span>
<span class="n">toastError</span><span class="o">(</span><span class="n">_scaffoldKey</span><span class="o">,</span> <span class="n">PlatformException</span><span class="o">(</span><span class="nl">code:</span> <span class="s">'signup'</span><span class="o">,</span> <span class="nl">message:</span> <span class="s">'Please check your password again.'</span><span class="o">));</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="k">try</span> <span class="o">{</span>
<span class="n">setState</span><span class="o">(()</span> <span class="o">=></span> <span class="n">_loading</span> <span class="o">=</span> <span class="kc">true</span><span class="o">);</span>
<span class="kd">final</span> <span class="n">r</span> <span class="o">=</span> <span class="n">await</span> <span class="n">FirebaseAuth</span><span class="o">.</span><span class="na">instance</span><span class="o">.</span><span class="na">createUserWithEmailAndPassword</span><span class="o">(</span>
<span class="nl">email:</span> <span class="n">emailInputController</span><span class="o">.</span><span class="na">text</span><span class="o">,</span>
<span class="nl">password:</span> <span class="n">passwordInputController</span><span class="o">.</span><span class="na">text</span>
<span class="o">);</span>
<span class="kd">final</span> <span class="n">userInfo</span> <span class="o">=</span> <span class="k">new</span> <span class="n">UserUpdateInfo</span><span class="o">();</span>
<span class="n">userInfo</span><span class="o">.</span><span class="na">photoUrl</span> <span class="o">=</span> <span class="s">'https://ssl.gstatic.com/ui/v1/icons/mail/rfr/logo_gmail_lockup_default_1x.png'</span><span class="o">;</span>
<span class="n">userInfo</span><span class="o">.</span><span class="na">displayName</span> <span class="o">=</span> <span class="n">firstNameInputController</span><span class="o">.</span><span class="na">text</span> <span class="o">+</span> <span class="s">' '</span> <span class="o">+</span> <span class="n">lastNameInputController</span><span class="o">.</span><span class="na">text</span><span class="o">;</span>
<span class="n">await</span> <span class="n">r</span><span class="o">.</span><span class="na">user</span><span class="o">.</span><span class="na">updateProfile</span><span class="o">(</span><span class="n">userInfo</span><span class="o">);</span>
<span class="n">await</span> <span class="n">r</span><span class="o">.</span><span class="na">user</span><span class="o">.</span><span class="na">reload</span><span class="o">();</span>
<span class="n">await</span> <span class="n">r</span><span class="o">.</span><span class="na">user</span><span class="o">.</span><span class="na">sendEmailVerification</span><span class="o">();</span>
<span class="n">Navigator</span><span class="o">.</span><span class="na">pushNamedAndRemoveUntil</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="s">'/auth'</span><span class="o">,</span> <span class="o">(</span><span class="n">route</span><span class="o">)</span> <span class="o">=></span> <span class="kc">false</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">toastError</span><span class="o">(</span><span class="n">_scaffoldKey</span><span class="o">,</span> <span class="n">e</span><span class="o">);</span>
<span class="n">print</span><span class="o">(</span><span class="n">e</span><span class="o">);</span>
<span class="o">}</span> <span class="k">finally</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">mounted</span><span class="o">)</span> <span class="n">setState</span><span class="o">(()</span> <span class="o">=></span> <span class="n">_loading</span> <span class="o">=</span> <span class="kc">false</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">},</span>
<span class="o">),</span>
</code></pre></div></div>
<p>파이어베이스 로그인을 하기 전에 전처리 검사인 폼검사와 패스워드 확인검사를 합니다.</p>
<p>파이어베이스 로그인(createUserWithEmailAndPassword)을 하고 나면 유저 정보를 얻을 수 있습니다.</p>
<p>이 때 updateProfile을 통해 displayName, photoUrl등을 변경해줍니다.(초기값 null)</p>
<blockquote>
<p>가입 당시 해주지 않으면 다른 화면에서 null 검사등이 매우 귀찮아 지기 때문입니다. 예제에는 의미 없는 링크를 넣어봤습니다.</p>
</blockquote>
<p>이메일 검증(sendEmailVerification())을 통해 해당 이메일 가입자에게 메일이 전송됩니다.</p>
<p>모든 페이지를 지우고 인증페이지로 갑니다.</p>
<h1 id="인증-화면">인증 화면</h1>
<h2 id="이메일-검사">이메일 검사</h2>
<p><strong>auth.dart</strong></p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">streamOpen</span><span class="o">()</span> <span class="o">{</span>
<span class="n">_subscriptionAuth</span> <span class="o">=</span> <span class="n">FirebaseAuth</span><span class="o">.</span><span class="na">instance</span><span class="o">.</span><span class="na">onAuthStateChanged</span><span class="o">.</span><span class="na">listen</span><span class="o">((</span><span class="n">fu</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">fu</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">Navigator</span><span class="o">.</span><span class="na">pushReplacementNamed</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="s">'/signin'</span><span class="o">);</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">setState</span><span class="o">(()</span> <span class="o">=></span> <span class="n">_firebaseUser</span> <span class="o">=</span> <span class="n">fu</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">fu</span><span class="o">.</span><span class="na">isEmailVerified</span><span class="o">)</span> <span class="o">{</span>
<span class="n">setState</span><span class="o">(()</span> <span class="o">=></span> <span class="n">_message</span> <span class="o">=</span> <span class="s">'Email is not authenticated.'</span><span class="o">);</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">Navigator</span><span class="o">.</span><span class="na">pushReplacementNamed</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="s">'/home'</span><span class="o">);</span>
<span class="o">});</span>
<span class="o">}</span>
</code></pre></div></div>
<p>로그인은 되어 있으나 이메일이 유효하지 않으면 메세지만 바꿔주고 다른 페이지로 이동시키지 않습니다.</p>
<p><strong>이메일 인증 확인 화면</strong><br />
<img src="/images/ff/2020-03-27_21.17.07.png" alt="alt email" /></p>
<p>이메일이 확인되지 않았을 때 화면입니다.</p>
<p><strong>이메일 확인</strong><br />
<img src="/images/ff/2020-03-27_21.11.48.png" alt="alt email confirmed" /></p>
<p>이메일을 확인하고 나면 위와 같은 페이지가 열립니다.</p>
<h2 id="이메일-확인-버튼">이메일 확인 버튼</h2>
<p><strong>auth.dart</strong></p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Widget</span> <span class="nf">_buildReloadButton</span><span class="p">(</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">RaisedButton</span><span class="o">(</span>
<span class="nl">child:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'Email Confirmed'</span><span class="o">),</span>
<span class="nl">onPressed:</span> <span class="o">()</span> <span class="n">async</span> <span class="o">{</span>
<span class="kd">final</span> <span class="n">u</span> <span class="o">=</span> <span class="n">await</span> <span class="n">FirebaseAuth</span><span class="o">.</span><span class="na">instance</span><span class="o">.</span><span class="na">currentUser</span><span class="o">();</span>
<span class="n">await</span> <span class="n">u</span><span class="o">.</span><span class="na">reload</span><span class="o">();</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">u</span><span class="o">.</span><span class="na">isEmailVerified</span><span class="o">)</span> <span class="o">{</span>
<span class="n">toastError</span><span class="o">(</span><span class="n">_scaffoldKey</span><span class="o">,</span> <span class="s">'Email is not authenticated. please try again'</span><span class="o">);</span>
<span class="k">return</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">Navigator</span><span class="o">.</span><span class="na">pushReplacementNamed</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="s">'/home'</span><span class="o">);</span>
<span class="o">},</span>
<span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>현재 사용자 정보를 가져와서 갱신합니다.</p>
<p>이메일 확인이 되었다면 메인페이지로 이동합니다.</p>
<blockquote>
<p>이메일 확인을 했다고 onAuthStateChanged 들어 오지 않기 때문에 이렇게 수동으로 뭔가 해줘야합니다. 타이머를 돌려서 체크하는 방법이 좋을 것 같은데 원리만 알려드리기 위해 간단히 꾸며봅니다.</p>
</blockquote>
<h1 id="소스">소스</h1>
<p>지난번 설명드렸던 부분은 모두 패스했고 모양을 내다보니 양이 많아서 아래 소스를 참고하시기 바랍니다.</p>
<p><a href="https://github.com/fkkmemi/ff2">https://github.com/fkkmemi/ff2</a></p>
<h1 id="영상">영상</h1>
<!-- Courtesy of embedresponsively.com //-->
<div class="responsive-video-container">
<iframe src="https://www.youtube-nocookie.com/embed/0xAHKCJ7Qhc" frameborder="0" allowfullscreen=""></iframe>
</div>memifkkmemi@gmail.com회원가입 페이지를 만들고 이메일로 회원가입을 해봅니다.Flutter와 Firebase로 Android iOS 둘 다 만들기 19 예외 처리하기2020-03-24T00:00:00+00:002020-03-24T00:00:00+00:00https://fkkmemi.github.io/ff/ff%20019<p>간단하게 예외처리에 대해 알아보고 플러터의 스낵바로 표현 해봅니다.</p>
<h1 id="개요">개요</h1>
<p>예외처리는 대부분의 상황에서 if else로 처리가 가능하지만, 잘 모르는 상황이 있을 수 있습니다. 특히 Future로 미래에 응답을 반환할 때 그런 경우가 많습니다.</p>
<h1 id="if-else로-처리가-가능한-상황">if else로 처리가 가능한 상황</h1>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="n">a</span> <span class="o">=</span> <span class="mi">3</span><span class="o">,</span> <span class="n">b</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
<span class="n">div</span> <span class="o">(</span><span class="n">a</span><span class="o">,</span> <span class="n">b</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">b</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="k">return</span> <span class="mi">0</span><span class="o">;</span>
<span class="k">return</span> <span class="n">a</span> <span class="o">/</span> <span class="n">b</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>일반적인 예외처리입니다.</p>
<blockquote>
<p>여러 플랫폼을 다룰 때 항상 먼저 해보는 에러처리는 습관 적으로 0으로 나누기를 해봅니다. dart는 0으로 나눌 경우 에러를 내지 않고 Infinity 라는 값을 줍니다.</p>
</blockquote>
<h1 id="if-else로-처리가-불가능한-상황">if else로 처리가 불가능한 상황</h1>
<p>지난번 코드의 이메일로 로그인하기를 해보면 알 수 있습니다.</p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">SignInButton</span><span class="o">(</span>
<span class="n">Buttons</span><span class="o">.</span><span class="na">Email</span><span class="o">,</span>
<span class="nl">onPressed:</span> <span class="o">()</span> <span class="n">async</span> <span class="o">{</span>
<span class="n">await</span> <span class="n">FirebaseAuth</span><span class="o">.</span><span class="na">instance</span><span class="o">.</span><span class="na">signInWithEmailAndPassword</span><span class="o">(</span>
<span class="nl">email:</span> <span class="n">emailController</span><span class="o">.</span><span class="na">text</span><span class="o">,</span>
<span class="nl">password:</span> <span class="n">passwordController</span><span class="o">.</span><span class="na">text</span><span class="o">,</span>
<span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<blockquote>
<p>이메일 로그인 메쏘드는 뻔한 곳에 뻔하게 잘 정의되어 있기 때문에 따로 설명은 필요 없을 것 같습니다.</p>
</blockquote>
<p>디버그 중에 눌러보면 이런 에러를 만날 수 있습니다.</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PlatformException(ERROR_OPERATION_NOT_ALLOWED, The given sign-in provider is disabled for this Firebase project. Enable it in the Firebase console, under the sign-in method tab of the Auth section., null)
</code></pre></div></div>
<p>파이어베이스 프로젝트에 이메일로그인 설정이 안되어 있다는 것입니다.</p>
<p>이것은 파이어베이스 콘솔 페이지에서 인증에서 쉽게 바꿀 수 있습니다.</p>
<blockquote>
<p>현재는 예외처리에 대한 내용이므로 회원가입때 다루겠습니다.</p>
</blockquote>
<p>에러만 나오는 것이 아니라 디버그 중이라 해당 위치에 브레이크가 걸리면서 동작이 멈추게 됩니다.</p>
<p>결국 에러 때문에 의도된 동작을 못할 때가 문제인 것입니다.</p>
<h1 id="try-catch">try catch</h1>
<p>async 함수 내부라면 간단하게 try catch로 잡아 낼 수 있습니다.</p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nl">onPressed:</span> <span class="o">()</span> <span class="n">async</span> <span class="o">{</span>
<span class="k">try</span> <span class="o">{</span>
<span class="n">await</span> <span class="n">FirebaseAuth</span><span class="o">.</span><span class="na">instance</span><span class="o">.</span><span class="na">signInWithEmailAndPassword</span><span class="o">(</span>
<span class="nl">email:</span> <span class="n">emailController</span><span class="o">.</span><span class="na">text</span><span class="o">,</span>
<span class="nl">password:</span> <span class="n">passwordController</span><span class="o">.</span><span class="na">text</span><span class="o">,</span>
<span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">print</span><span class="o">(</span><span class="n">e</span><span class="o">);</span>
<span class="n">print</span><span class="o">(</span><span class="n">e</span><span class="o">.</span><span class="na">runtimeType</span><span class="o">);</span>
<span class="n">print</span><span class="o">(</span><span class="n">e</span><span class="o">.</span><span class="na">code</span><span class="o">);</span>
<span class="n">print</span><span class="o">(</span><span class="n">e</span><span class="o">.</span><span class="na">message</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>catch의 e는 dynamic 입니다.</p>
<p>어떤 형태로든 에러를 만들 수 있는 것입니다.</p>
<p>FirebaseAuth가 만든 e라는 놈은 PlatformException 라는 형태이지만 다른 형태로도 만들 수 있는 것입니다.</p>
<p>참고: <a href="https://api.flutter.dev/flutter/services/PlatformException-class.html">https://api.flutter.dev/flutter/services/PlatformException-class.html</a></p>
<p>PlatformException은 플러터의 서비스 (<em>flutter/services.dart</em>) 에 추가 되어 있는 것입니다.</p>
<ul>
<li>PlatformException
<ul>
<li>code(String)</li>
<li>message(String)</li>
<li>detail(dynamic)</li>
</ul>
</li>
</ul>
<p>그러니 e.runtimeType이 PlatformException이 아니라면 e.message는 없을 수도 있으니 주의해야합니다.</p>
<p>화면에 표시해야 할 것은 e.message 가 되는 것입니다.</p>
<p>혹은 e.code로 다양한 출력이나 국제화를 고려한 코딩을 할 수도 있습니다.</p>
<h1 id="throw">throw</h1>
<p>일부러 에러를 내고 싶을 때도 있습니다.</p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">try</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">passwordController</span><span class="o">.</span><span class="na">text</span><span class="o">.</span><span class="na">length</span> <span class="o"><</span> <span class="mi">4</span><span class="o">)</span> <span class="k">throw</span><span class="o">(</span><span class="s">'short!!!'</span><span class="o">);</span>
<span class="n">await</span> <span class="n">FirebaseAuth</span><span class="o">.</span><span class="na">instance</span><span class="o">.</span><span class="na">signInWithEmailAndPassword</span><span class="o">(</span>
<span class="nl">email:</span> <span class="n">emailController</span><span class="o">.</span><span class="na">text</span><span class="o">,</span>
<span class="nl">password:</span> <span class="n">passwordController</span><span class="o">.</span><span class="na">text</span><span class="o">,</span>
<span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">print</span><span class="o">(</span><span class="n">e</span><span class="o">);</span>
<span class="n">print</span><span class="o">(</span><span class="n">e</span><span class="o">.</span><span class="na">runtimeType</span><span class="o">);</span>
<span class="n">print</span><span class="o">(</span><span class="n">e</span><span class="o">.</span><span class="na">message</span><span class="o">);</span>
<span class="n">print</span><span class="o">(</span><span class="n">e</span><span class="o">.</span><span class="na">code</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>보통 catch에서 공통 UI로 처리하고 싶을 때 이렇게 하는데요..</p>
<p>위에서 언급한대로 다양한 에러를 만들 수 있는데 위의 예시는 String을 넘긴 것입니다.</p>
<p>그래서 e.message나 e.code는 당연히 없는 것이죠..</p>
<p>FirebaseAuth 처럼 에러를 만드려면 똑같은 인스턴스를 만들면 됩니다.</p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">throw</span><span class="o">(</span><span class="n">PlatformException</span><span class="o">(</span><span class="nl">code:</span> <span class="s">'xx'</span><span class="o">,</span> <span class="nl">message:</span> <span class="s">'short!!'</span><span class="o">));</span>
</code></pre></div></div>
<h1 id="finally">finally</h1>
<p>에러가 나든 안나든 최종적으로 해야할 일이 필요할 때가 있습니다.</p>
<p>로딩을 보여준다고 하면</p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">try</span> <span class="o">{</span>
<span class="n">setState</span><span class="o">(()</span> <span class="o">=></span> <span class="n">_loading</span> <span class="o">=</span> <span class="kc">true</span><span class="o">);</span>
<span class="n">await</span> <span class="n">FirebaseAuth</span><span class="o">.</span><span class="na">instance</span><span class="o">.</span><span class="na">signInWithEmailAndPassword</span><span class="o">(</span>
<span class="nl">email:</span> <span class="n">emailController</span><span class="o">.</span><span class="na">text</span><span class="o">,</span>
<span class="nl">password:</span> <span class="n">passwordController</span><span class="o">.</span><span class="na">text</span><span class="o">,</span>
<span class="o">);</span>
<span class="n">setState</span><span class="o">(()</span> <span class="o">=></span> <span class="n">_loading</span> <span class="o">=</span> <span class="kc">false</span><span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">print</span><span class="o">(</span><span class="n">e</span><span class="o">.</span><span class="na">message</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>에러가 나든 안나든 로딩을 꺼줘야하는데 이렇게 하면 에러가 났을 때 로딩이 꺼지지 않습니다.</p>
<p>그럴 때 finally를 사용하면 됩니다.</p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">try</span> <span class="o">{</span>
<span class="n">setState</span><span class="o">(()</span> <span class="o">=></span> <span class="n">_loading</span> <span class="o">=</span> <span class="kc">true</span><span class="o">);</span>
<span class="n">await</span> <span class="n">FirebaseAuth</span><span class="o">.</span><span class="na">instance</span><span class="o">.</span><span class="na">signInWithEmailAndPassword</span><span class="o">(</span>
<span class="nl">email:</span> <span class="n">emailController</span><span class="o">.</span><span class="na">text</span><span class="o">,</span>
<span class="nl">password:</span> <span class="n">passwordController</span><span class="o">.</span><span class="na">text</span><span class="o">,</span>
<span class="o">);</span>
<span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="n">print</span><span class="o">(</span><span class="n">e</span><span class="o">.</span><span class="na">message</span><span class="o">);</span>
<span class="o">}</span> <span class="k">finally</span> <span class="o">{</span>
<span class="n">setState</span><span class="o">(()</span> <span class="o">=></span> <span class="n">_loading</span> <span class="o">=</span> <span class="kc">false</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>이러면 에러가 나든 안나든 로딩은 꺼지게 됩니다.</p>
<blockquote>
<p>예외처리는 자스와 비슷한 느낌을 많이 받습니다. 제가 자스를 많이 다루는 쪽이라 설명이나 코딩이 비슷해지는 것일 수도 있습니다. 제가 꼭 정답은 아니니 참고만 하시고 각자의 스타일대로 하시면 됩니다.</p>
</blockquote>
<h1 id="스낵바로-에러-표시하기">스낵바로 에러 표시하기</h1>
<p>화면 하단에 일정시간 동안 간단한 정보를 표시하는 형태를 스낵바 혹은 토스트라 합니다.</p>
<p>참고: <a href="https://flutter-ko.dev/docs/cookbook/design/snackbars">https://flutter-ko.dev/docs/cookbook/design/snackbars</a></p>
<p>플러터에서 기본으로 제공하는 스낵바 예제로 보면 코드는 단순합니다.</p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="n">snackBar</span> <span class="o">=</span> <span class="n">SnackBar</span><span class="o">(</span><span class="nl">content:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'Yay! A SnackBar!'</span><span class="o">));</span>
<span class="n">Scaffold</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="n">context</span><span class="o">).</span><span class="na">showSnackBar</span><span class="o">(</span><span class="n">snackBar</span><span class="o">);</span>
</code></pre></div></div>
<p>아쉽게도 이렇게 테스트하면 동작하지 않습니다.</p>
<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Scaffold.of() called with a context that does not contain a Scaffold
</code></pre></div></div>
<p>Scaffold가 포함된 클래스 내부에서는 동작하지 않는 것입니다.</p>
<p>고작 스낵바 만들려고 위의 예제 처럼 클래스를 하나 만들어줘야하는 것입니다.</p>
<p>그럴 때 키로 호출하는 방법이 있습니다.</p>
<p>지난번 폼 검사할때 처럼 스캐폴드도 스테이트를 키를 만들어 줍니다.</p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="n">_scaffoldKey</span> <span class="o">=</span> <span class="n">GlobalKey</span><span class="o"><</span><span class="n">ScaffoldState</span><span class="o">>();</span>
<span class="nd">@override</span>
<span class="n">Widget</span> <span class="nf">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Scaffold</span><span class="o">(</span>
<span class="nl">key:</span> <span class="n">_scaffoldKey</span><span class="o">,</span>
<span class="nl">appBar:</span> <span class="n">AppBar</span><span class="o">(</span><span class="nl">title:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'Sign in'</span><span class="o">),</span> <span class="o">),</span>
<span class="c1">// ..</span>
<span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>이제 scaffold의 키로 클래스 내부에서 스낵바를 호출 할 수 있습니다.</p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="n">snackBar</span> <span class="o">=</span> <span class="n">SnackBar</span><span class="o">(</span><span class="nl">content:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'Yay! A SnackBar!'</span><span class="o">));</span>
<span class="n">_scaffoldKey</span><span class="o">.</span><span class="na">currentState</span><span class="o">.</span><span class="na">showSnackBar</span><span class="o">(</span><span class="n">snackBar</span><span class="o">);</span>
</code></pre></div></div>
<h2 id="메써드로-만들기">메써드로 만들기</h2>
<p>다양한 곳에서 사용하기 위해…</p>
<p><strong>./methods/toast.dart</strong></p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>
<span class="kn">import</span> <span class="s">'package:flutter/services.dart'</span><span class="o">;</span>
<span class="n">toastError</span><span class="o">(</span><span class="n">GlobalKey</span><span class="o"><</span><span class="n">ScaffoldState</span><span class="o">></span> <span class="n">key</span><span class="o">,</span> <span class="kd">dynamic</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="kt">String</span> <span class="n">message</span> <span class="o">=</span> <span class="s">'unknown error'</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">e</span> <span class="k">is</span> <span class="n">PlatformException</span><span class="o">)</span> <span class="n">message</span> <span class="o">=</span> <span class="n">e</span><span class="o">.</span><span class="na">message</span><span class="o">;</span>
<span class="kd">final</span> <span class="n">snackBar</span> <span class="o">=</span> <span class="n">SnackBar</span><span class="o">(</span><span class="nl">content:</span> <span class="n">Text</span><span class="o">(</span><span class="n">message</span><span class="o">));</span>
<span class="n">key</span><span class="o">.</span><span class="na">currentState</span><span class="o">.</span><span class="na">showSnackBar</span><span class="o">(</span><span class="n">snackBar</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>이렇게 정리해봅니다.</p>
<blockquote>
<p>역시 자스(vue.js 같은..)에서 하던 느낌으로 대부분의 코딩을 정리하게 됩니다.</p>
</blockquote>
<h2 id="fluttertoast">fluttertoast</h2>
<p>기본 스낵바가 귀찮거나, 좀 더 색다른 표시를 원하시면 외부 모듈을 사용하는 것도 방법입니다.</p>
<blockquote>
<p>스캐폴드 위치 상관 없이 아무데서나 잘 튀어나오는 것을 확인했습니다.<br />
주변요소와 잘 어울리게 하면 좋지만… 저는 디자인 감이 떨어져서 그냥 기본 스낵바로 돌아왔습니다..</p>
</blockquote>
<p>참고: <a href="https://pub.dev/packages/fluttertoast">https://pub.dev/packages/fluttertoast</a></p>
<h1 id="마치며">마치며</h1>
<p>로그인,회원가입등에서 사용하기 위해 최소한의 장치만 갖춘 것입니다.</p>
<p>위에서 언급한대로 제 코드는 주류코드가 아닙니다.</p>
<p>관리의 편의를 위해 웹쪽과 비슷한 느낌으로 코딩하는 것이니 개념만 이해하고 직접 구현해보시는 것이 나을 수도 있습니다.</p>
<h1 id="소스">소스</h1>
<p><a href="https://github.com/fkkmemi/ff2">https://github.com/fkkmemi/ff2</a></p>
<h1 id="영상">영상</h1>
<!-- Courtesy of embedresponsively.com //-->
<div class="responsive-video-container">
<iframe src="https://www.youtube-nocookie.com/embed/2nj9c0eFZ_A" frameborder="0" allowfullscreen=""></iframe>
</div>memifkkmemi@gmail.com간단하게 예외처리에 대해 알아보고 플러터의 스낵바로 표현 해봅니다.Flutter와 Firebase로 Android iOS 둘 다 만들기 18 로그인 화면 꾸미기2020-03-19T00:00:00+00:002020-03-19T00:00:00+00:00https://fkkmemi.github.io/ff/ff%20018<p>이메일로그인을 구현하기 전에 모양을 간단히 꾸며봅니다.</p>
<h1 id="개요">개요</h1>
<p>앱을 제작하며 의외로 플러터에서 가장 어려운 것이 화면 구성이었습니다.</p>
<p>별 것 아닐 것 같은데 막상 해보면 화면 밖으로 나가기도하고 스크롤 에러나고 하기 때문에.. 매우 어렵습니다.</p>
<p>물론 초기 개발때 괴로운 부분이며 익숙해지면 그럭저럭 할만합니다.</p>
<p>미루지말고 바로 화면구성을 해보면서 익숙해지면됩니다.</p>
<p>방법은 다양하지만 간단하게 <strong>꼬지</strong> 않고 만들어보겠습니다.</p>
<h1 id="위젯-쪼개기">위젯 쪼개기</h1>
<p>작업하다보면 귀찮아서 마구잡이로 탭이 늘어가는 형태가 만들어지는데 나중에 관리가 안됩니다.</p>
<blockquote>
<p>확실히 파이썬이 엔드브라켓을 빼고 과감하게 탭 파싱을 한것은 잘한 짓 같습니다.</p>
</blockquote>
<p>eg) 리스트형 쪼개기 예</p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Widget</span> <span class="nf">_buildSubItem</span><span class="p">(</span><span class="o">)</span>
<span class="n">Widget</span> <span class="nf">_buildItem</span><span class="p">(</span><span class="o">)</span>
<span class="n">Widget</span> <span class="nf">_buildList</span><span class="p">(</span><span class="o">)</span>
<span class="n">Widget</span> <span class="nf">_buildBody</span><span class="p">(</span><span class="o">)</span>
<span class="nd">@override</span>
<span class="n">Widget</span> <span class="nf">build</span><span class="p">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Scaffold</span><span class="o">(</span>
<span class="nl">appBar:</span> <span class="n">AppBar</span><span class="o">(</span><span class="nl">title:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'SignInPage'</span><span class="o">),</span> <span class="o">),</span>
<span class="nl">body:</span> <span class="n">_buildBody</span><span class="o">()</span>
<span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<h1 id="스크롤-되게-만들기">스크롤 되게 만들기</h1>
<p>자주하는 실수 인데 적당한 높이를 가지고 있을 때 대충 Container에 이것저것 넣고 돌려보면 작은 화면 기기에서 에러가 납니다.</p>
<p>플러터에 뭔가를 표시할 때 늘 생각해야할 것이 크기입니다.</p>
<p>SingleChildScrollView로 감싸서 혹시나 길어지는 컨텐츠도 안전하게 보여집니다.</p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">return</span> <span class="nf">SingleChildScrollView</span><span class="p">(</span>
<span class="nl">child:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'good'</span><span class="o">)</span>
<span class="o">);</span>
</code></pre></div></div>
<h1 id="여백-주기">여백 주기</h1>
<p>컨테이너를 생성해서 패딩을 줍니다.</p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">return</span> <span class="nf">SingleChildScrollView</span><span class="p">(</span>
<span class="nl">child:</span> <span class="n">Container</span><span class="o">(</span>
<span class="nl">padding:</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="o">(</span><span class="mi">10</span><span class="o">),</span>
<span class="nl">child:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'good'</span><span class="o">),</span>
<span class="o">),</span>
<span class="o">);</span>
</code></pre></div></div>
<h1 id="폼-감싸기">폼 감싸기</h1>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="n">_formKey</span> <span class="o">=</span> <span class="n">GlobalKey</span><span class="o"><</span><span class="n">FormState</span><span class="o">>();</span>
<span class="k">return</span> <span class="nf">SingleChildScrollView</span><span class="p">(</span>
<span class="nl">child:</span> <span class="n">Container</span><span class="o">(</span>
<span class="nl">padding:</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="o">(</span><span class="mi">10</span><span class="o">),</span>
<span class="nl">child:</span> <span class="n">Form</span><span class="o">(</span>
<span class="nl">key:</span> <span class="n">_formKey</span><span class="o">,</span>
<span class="nl">child:</span> <span class="n">Column</span><span class="o">(</span>
<span class="nl">children:</span> <span class="o"><</span><span class="n">Widget</span><span class="o">>[</span>
<span class="n">Text</span><span class="o">(</span><span class="s">'id'</span><span class="o">),</span>
<span class="n">Text</span><span class="o">(</span><span class="s">'pwd'</span><span class="o">)</span>
<span class="o">],</span>
<span class="o">),</span>
<span class="o">),</span>
<span class="o">),</span>
<span class="o">);</span>
</code></pre></div></div>
<p>폼을 사용하는 목적중 하나가 유효성 판단이기 때문에 키를 지정해줍니다.</p>
<h1 id="입력부-모양-내기">입력부 모양 내기</h1>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">final</span> <span class="n">TextEditingController</span> <span class="n">emailController</span> <span class="o">=</span> <span class="n">TextEditingController</span><span class="o">();</span>
<span class="kd">final</span> <span class="n">TextEditingController</span> <span class="n">passwordController</span> <span class="o">=</span> <span class="n">TextEditingController</span><span class="o">();</span>
</code></pre></div></div>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">TextFormField</span><span class="o">(</span>
<span class="nl">decoration:</span> <span class="n">InputDecoration</span><span class="o">(</span>
<span class="nl">labelText:</span> <span class="s">'Email'</span><span class="o">,</span>
<span class="nl">hintText:</span> <span class="s">'eg) johndoe@abc.com'</span><span class="o">,</span>
<span class="nl">border:</span> <span class="n">OutlineInputBorder</span><span class="o">()</span>
<span class="o">),</span>
<span class="nl">controller:</span> <span class="n">emailController</span><span class="o">,</span>
<span class="nl">keyboardType:</span> <span class="n">TextInputType</span><span class="o">.</span><span class="na">emailAddress</span><span class="o">,</span>
<span class="o">),</span>
<span class="n">TextFormField</span><span class="o">(</span>
<span class="nl">decoration:</span> <span class="n">InputDecoration</span><span class="o">(</span>
<span class="nl">labelText:</span> <span class="s">'Password'</span><span class="o">,</span>
<span class="nl">hintText:</span> <span class="s">'eg) very difficult text'</span><span class="o">,</span>
<span class="nl">border:</span> <span class="n">OutlineInputBorder</span><span class="o">()</span>
<span class="o">),</span>
<span class="nl">controller:</span> <span class="n">passwordController</span><span class="o">,</span>
<span class="nl">obscureText:</span> <span class="kc">true</span><span class="o">,</span>
<span class="o">),</span>
</code></pre></div></div>
<p>decoration을 통해 다양한 모양을 낼 수 있는데 제가 좋아하는 스타일로 만들어봤습니다.</p>
<h1 id="간격-띄우기">간격 띄우기</h1>
<p>가끔 위젯끼리 달라붙어 있을 때 적당하게 띄우는 것이 필요한데 의외로 혼란스럽습니다.</p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Padding</span><span class="o">(</span><span class="nl">padding:</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="o">(</span><span class="mi">10</span><span class="o">),),</span>
<span class="n">Container</span><span class="o">(</span><span class="nl">height:</span> <span class="mi">10</span><span class="o">,),</span>
<span class="n">SizedBox</span><span class="o">(</span><span class="nl">height:</span> <span class="mi">10</span><span class="o">,),</span>
</code></pre></div></div>
<p>방법이 너무 다양해서 문제인데.. 가장 간단한 SizedBox가 용도에 맞을 것 같습니다.</p>
<h1 id="로그인-버튼-만들기">로그인 버튼 만들기</h1>
<p>어느정도 데코가 들어간 버튼 만들기가 생각보다 어렵습니다.</p>
<p>만약 아이콘 버튼을 만든다면..</p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">RaisedButton</span><span class="o">(</span>
<span class="nl">color:</span> <span class="n">Theme</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="n">context</span><span class="o">).</span><span class="na">primaryColor</span><span class="o">,</span>
<span class="nl">textColor:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">white</span><span class="o">,</span>
<span class="nl">child:</span> <span class="n">Row</span><span class="o">(</span>
<span class="nl">mainAxisAlignment:</span> <span class="n">MainAxisAlignment</span><span class="o">.</span><span class="na">spaceAround</span><span class="o">,</span>
<span class="nl">children:</span> <span class="o"><</span><span class="n">Widget</span><span class="o">>[</span>
<span class="n">Icon</span><span class="o">(</span><span class="n">Icons</span><span class="o">.</span><span class="na">email</span><span class="o">),</span>
<span class="n">Text</span><span class="o">(</span><span class="s">'Sign in with Email'</span><span class="o">)</span>
<span class="o">],</span>
<span class="o">),</span>
<span class="o">),</span>
</code></pre></div></div>
<p>대략 이런 느낌으로 만들어야됩니다. 주렁주렁 데코를 하다보면 시간이 꽤나 갑니다.</p>
<p>그래서 pub.dev의 모듈을 써서 꾸며봤습니다.</p>
<p>참고: <a href="https://pub.dev/packages/flutter_signin_button">https://pub.dev/packages/flutter_signin_button</a></p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter_signin_button/flutter_signin_button.dart'</span><span class="o">;</span>
<span class="n">SignInButton</span><span class="o">(</span><span class="n">Buttons</span><span class="o">.</span><span class="na">Email</span><span class="o">,</span> <span class="nl">onPressed:</span> <span class="o">()</span> <span class="o">{}),</span>
<span class="n">SignInButton</span><span class="o">(</span><span class="n">Buttons</span><span class="o">.</span><span class="na">Google</span><span class="o">,</span> <span class="nl">onPressed:</span> <span class="o">()</span> <span class="o">{}),</span>
</code></pre></div></div>
<h1 id="전체-코드">전체 코드</h1>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>
<span class="kn">import</span> <span class="s">'package:firebase_auth/firebase_auth.dart'</span><span class="o">;</span>
<span class="kn">import</span> <span class="s">'package:google_sign_in/google_sign_in.dart'</span><span class="o">;</span>
<span class="kn">import</span> <span class="s">'package:flutter_signin_button/flutter_signin_button.dart'</span><span class="o">;</span>
<span class="kd">class</span> <span class="nc">SignInPage</span> <span class="kd">extends</span> <span class="n">StatefulWidget</span> <span class="o">{</span>
<span class="n">SignInPage</span><span class="o">({</span><span class="n">Key</span> <span class="n">key</span><span class="o">})</span> <span class="o">:</span> <span class="k">super</span><span class="o">(</span><span class="nl">key:</span> <span class="n">key</span><span class="o">);</span>
<span class="kd">static</span> <span class="kd">const</span> <span class="n">routeName</span> <span class="o">=</span> <span class="s">'/signin'</span><span class="o">;</span>
<span class="nd">@override</span>
<span class="n">_SignInPageState</span> <span class="n">createState</span><span class="o">()</span> <span class="o">=></span> <span class="n">_SignInPageState</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">class</span> <span class="nc">_SignInPageState</span> <span class="kd">extends</span> <span class="n">State</span><span class="o"><</span><span class="n">SignInPage</span><span class="o">></span> <span class="o">{</span>
<span class="kt">bool</span> <span class="n">_loading</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="kd">final</span> <span class="n">_formKey</span> <span class="o">=</span> <span class="n">GlobalKey</span><span class="o"><</span><span class="n">FormState</span><span class="o">>();</span>
<span class="kd">final</span> <span class="n">TextEditingController</span> <span class="n">emailController</span> <span class="o">=</span> <span class="n">TextEditingController</span><span class="o">();</span>
<span class="kd">final</span> <span class="n">TextEditingController</span> <span class="n">passwordController</span> <span class="o">=</span> <span class="n">TextEditingController</span><span class="o">();</span>
<span class="n">_googleSignIn</span> <span class="o">()</span> <span class="n">async</span> <span class="o">{</span>
<span class="kd">final</span> <span class="kt">bool</span> <span class="n">isSignedIn</span> <span class="o">=</span> <span class="n">await</span> <span class="n">GoogleSignIn</span><span class="o">().</span><span class="na">isSignedIn</span><span class="o">();</span>
<span class="n">GoogleSignInAccount</span> <span class="n">googleUser</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">isSignedIn</span><span class="o">)</span> <span class="n">googleUser</span> <span class="o">=</span> <span class="n">await</span> <span class="n">GoogleSignIn</span><span class="o">().</span><span class="na">signInSilently</span><span class="o">();</span>
<span class="k">else</span> <span class="n">googleUser</span> <span class="o">=</span> <span class="n">await</span> <span class="n">GoogleSignIn</span><span class="o">().</span><span class="na">signIn</span><span class="o">();</span>
<span class="kd">final</span> <span class="n">GoogleSignInAuthentication</span> <span class="n">googleAuth</span> <span class="o">=</span> <span class="n">await</span> <span class="n">googleUser</span><span class="o">.</span><span class="na">authentication</span><span class="o">;</span>
<span class="kd">final</span> <span class="n">AuthCredential</span> <span class="n">credential</span> <span class="o">=</span> <span class="n">GoogleAuthProvider</span><span class="o">.</span><span class="na">getCredential</span><span class="o">(</span>
<span class="nl">accessToken:</span> <span class="n">googleAuth</span><span class="o">.</span><span class="na">accessToken</span><span class="o">,</span>
<span class="nl">idToken:</span> <span class="n">googleAuth</span><span class="o">.</span><span class="na">idToken</span><span class="o">,</span>
<span class="o">);</span>
<span class="kd">final</span> <span class="n">FirebaseUser</span> <span class="n">user</span> <span class="o">=</span> <span class="o">(</span><span class="n">await</span> <span class="n">FirebaseAuth</span><span class="o">.</span><span class="na">instance</span><span class="o">.</span><span class="na">signInWithCredential</span><span class="o">(</span><span class="n">credential</span><span class="o">)).</span><span class="na">user</span><span class="o">;</span>
<span class="c1">// print("signed in " + user.displayName);</span>
<span class="k">return</span> <span class="n">user</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">_buildLoading</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Center</span><span class="o">(</span><span class="nl">child:</span> <span class="n">CircularProgressIndicator</span><span class="o">(),);</span>
<span class="o">}</span>
<span class="n">_buildBody</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">SingleChildScrollView</span><span class="o">(</span>
<span class="nl">child:</span> <span class="n">Container</span><span class="o">(</span>
<span class="nl">padding:</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="o">(</span><span class="mi">10</span><span class="o">),</span>
<span class="nl">child:</span> <span class="n">Form</span><span class="o">(</span>
<span class="nl">key:</span> <span class="n">_formKey</span><span class="o">,</span>
<span class="nl">child:</span> <span class="n">Column</span><span class="o">(</span>
<span class="nl">children:</span> <span class="o"><</span><span class="n">Widget</span><span class="o">>[</span>
<span class="n">TextFormField</span><span class="o">(</span>
<span class="nl">decoration:</span> <span class="n">InputDecoration</span><span class="o">(</span>
<span class="nl">labelText:</span> <span class="s">'Email'</span><span class="o">,</span>
<span class="nl">hintText:</span> <span class="s">'eg) johndoe@xxx.com'</span><span class="o">,</span>
<span class="nl">border:</span> <span class="n">OutlineInputBorder</span><span class="o">(),</span>
<span class="o">),</span>
<span class="nl">controller:</span> <span class="n">emailController</span><span class="o">,</span>
<span class="nl">keyboardType:</span> <span class="n">TextInputType</span><span class="o">.</span><span class="na">emailAddress</span><span class="o">,</span>
<span class="o">),</span>
<span class="c1">// Container(height: 10,),</span>
<span class="n">SizedBox</span><span class="o">(</span><span class="nl">height:</span> <span class="mi">10</span><span class="o">,),</span>
<span class="n">TextFormField</span><span class="o">(</span>
<span class="nl">decoration:</span> <span class="n">InputDecoration</span><span class="o">(</span>
<span class="nl">labelText:</span> <span class="s">'Password'</span><span class="o">,</span>
<span class="nl">hintText:</span> <span class="s">'eg) very hard key'</span><span class="o">,</span>
<span class="nl">border:</span> <span class="n">OutlineInputBorder</span><span class="o">(),</span>
<span class="o">),</span>
<span class="nl">controller:</span> <span class="n">passwordController</span><span class="o">,</span>
<span class="nl">obscureText:</span> <span class="kc">true</span><span class="o">,</span>
<span class="o">),</span>
<span class="n">SizedBox</span><span class="o">(</span><span class="nl">height:</span> <span class="mi">10</span><span class="o">,),</span>
<span class="n">SignInButton</span><span class="o">(</span>
<span class="n">Buttons</span><span class="o">.</span><span class="na">Email</span><span class="o">,</span>
<span class="nl">onPressed:</span> <span class="o">()</span> <span class="o">{},</span>
<span class="o">),</span>
<span class="n">Text</span><span class="o">(</span><span class="s">'or'</span><span class="o">),</span>
<span class="n">SignInButton</span><span class="o">(</span>
<span class="n">Buttons</span><span class="o">.</span><span class="na">Google</span><span class="o">,</span>
<span class="nl">onPressed:</span> <span class="o">()</span> <span class="n">async</span> <span class="o">{</span>
<span class="n">setState</span><span class="o">(()</span> <span class="o">=></span> <span class="n">_loading</span> <span class="o">=</span> <span class="kc">true</span><span class="o">);</span>
<span class="n">await</span> <span class="n">_googleSignIn</span><span class="o">();</span>
<span class="n">setState</span><span class="o">(()</span> <span class="o">=></span> <span class="n">_loading</span> <span class="o">=</span> <span class="kc">false</span><span class="o">);</span>
<span class="c1">// Navigator.pushReplacementNamed(context, '/');</span>
<span class="c1">// Navigator.pushReplacementNamed(context, '/auth');</span>
<span class="n">Navigator</span><span class="o">.</span><span class="na">pushReplacementNamed</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="s">'/home'</span><span class="o">);</span>
<span class="o">},</span>
<span class="o">),</span>
<span class="n">SizedBox</span><span class="o">(</span><span class="nl">height:</span> <span class="mi">20</span><span class="o">,),</span>
<span class="n">Text</span><span class="o">(</span><span class="s">"Don't have an account yet?"</span><span class="o">),</span>
<span class="n">FlatButton</span><span class="o">(</span>
<span class="nl">child:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'Sign up'</span><span class="o">),</span>
<span class="nl">onPressed:</span> <span class="o">()</span> <span class="o">{</span>
<span class="n">Navigator</span><span class="o">.</span><span class="na">pushNamed</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="s">'/signup'</span><span class="o">);</span>
<span class="o">},</span>
<span class="o">)</span>
<span class="o">],</span>
<span class="o">),</span>
<span class="o">),</span>
<span class="o">),</span>
<span class="o">);</span>
<span class="o">}</span>
<span class="nd">@override</span>
<span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Scaffold</span><span class="o">(</span>
<span class="nl">appBar:</span> <span class="n">AppBar</span><span class="o">(</span><span class="nl">title:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'Sign in'</span><span class="o">),</span> <span class="o">),</span>
<span class="nl">body:</span> <span class="n">_loading</span> <span class="o">?</span> <span class="n">_buildLoading</span><span class="o">()</span> <span class="o">:</span> <span class="n">_buildBody</span><span class="o">()</span>
<span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p><img src="/images/ff/2020-03-19_20.27.28.png" alt="alt iphone" /></p>
<h1 id="소스">소스</h1>
<p><a href="https://github.com/fkkmemi/ff2">https://github.com/fkkmemi/ff2</a></p>
<h1 id="영상">영상</h1>
<!-- Courtesy of embedresponsively.com //-->
<div class="responsive-video-container">
<iframe src="https://www.youtube-nocookie.com/embed/MSCOMGiinKs" frameborder="0" allowfullscreen=""></iframe>
</div>memifkkmemi@gmail.com이메일로그인을 구현하기 전에 모양을 간단히 꾸며봅니다.Flutter와 Firebase로 Android iOS 둘 다 만들기 16 form 작성해보기2020-03-17T00:00:00+00:002020-03-17T00:00:00+00:00https://fkkmemi.github.io/ff/ff%20016<p>TextFormInputField를 사용해서 입력부를 구현해봅니다.</p>
<h1 id="개요">개요</h1>
<p>이메일 패스워드를 사용해서 회원가입을 할 경우 기본적으로 이메일 형식 검사와 패스워드 확인 절차정도는 기본입니다.</p>
<p>이미 잘 짜여져있는 플러터의 폼검사 기능을 이용해서 간단히 구현해봅니다.</p>
<h1 id="방법">방법</h1>
<p>참고: <a href="https://flutter-ko.dev/docs/cookbook#forms">https://flutter-ko.dev/docs/cookbook#forms</a></p>
<p>방법은 여러가지 있는데 그 중 몇가지 샘플을 만들어 보겠습니다.</p>
<h2 id="onchange를-이용하기">onchange를 이용하기</h2>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">SignUp</span> <span class="kd">extends</span> <span class="n">StatefulWidget</span> <span class="o">{</span>
<span class="n">SignUp</span><span class="o">({</span><span class="n">Key</span> <span class="n">key</span><span class="o">})</span> <span class="o">:</span> <span class="k">super</span><span class="o">(</span><span class="nl">key:</span> <span class="n">key</span><span class="o">);</span>
<span class="nd">@override</span>
<span class="n">_SignUpState</span> <span class="n">createState</span><span class="o">()</span> <span class="o">=></span> <span class="n">_SignUpState</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">class</span> <span class="nc">_SignUpState</span> <span class="kd">extends</span> <span class="n">State</span><span class="o"><</span><span class="n">SignUp</span><span class="o">></span> <span class="o">{</span>
<span class="kt">String</span> <span class="n">_text</span> <span class="o">=</span> <span class="s">''</span><span class="o">;</span>
<span class="nd">@override</span>
<span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Scaffold</span><span class="o">(</span>
<span class="nl">appBar:</span> <span class="n">AppBar</span><span class="o">(</span><span class="nl">title:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'SignUp'</span><span class="o">)),</span>
<span class="nl">body:</span> <span class="n">Container</span><span class="o">(</span>
<span class="nl">padding:</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="o">(</span><span class="mi">10</span><span class="o">),</span>
<span class="nl">child:</span> <span class="n">Column</span><span class="o">(</span>
<span class="nl">children:</span> <span class="o"><</span><span class="n">Widget</span><span class="o">>[</span>
<span class="n">TextFormField</span><span class="o">(</span>
<span class="nl">initialValue:</span> <span class="s">''</span><span class="o">,</span>
<span class="nl">onChanged:</span> <span class="o">(</span><span class="n">v</span><span class="o">)</span> <span class="o">{</span>
<span class="n">setState</span><span class="o">(()</span> <span class="o">=></span> <span class="n">_text</span> <span class="o">=</span> <span class="n">v</span><span class="o">);</span>
<span class="o">},</span>
<span class="o">),</span>
<span class="n">RaisedButton</span><span class="o">(</span>
<span class="nl">child:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'submit!'</span><span class="o">),</span>
<span class="nl">onPressed:</span> <span class="o">()</span> <span class="o">{</span>
<span class="n">print</span><span class="o">(</span><span class="n">_text</span><span class="o">);</span>
<span class="n">setState</span><span class="o">(()</span> <span class="o">=></span> <span class="n">_text</span> <span class="o">=</span> <span class="s">''</span><span class="o">);</span>
<span class="o">},</span>
<span class="o">)</span>
<span class="o">],</span>
<span class="o">),</span>
<span class="o">),</span>
<span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>인풋에 뭔가를 적을 때마다 _text라는 변수를 채우고 버튼을 누를 때 해당정보를 확인할 수 있습니다.</p>
<p>문제는 setState(() => _text = ‘’);로 의도한 대로 지워지지 않는 다는 것입니다.</p>
<p>이런 방법은 그래서 거의 안쓰일 것 같습니다.</p>
<blockquote>
<p>사실 이 방법으로 앱을 배포하기도 했습니다.. 뭐 일단 돌아는 가니까요..</p>
</blockquote>
<h2 id="콘트롤러-사용하기">콘트롤러 사용하기</h2>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">_SignUpState</span> <span class="kd">extends</span> <span class="n">State</span><span class="o"><</span><span class="n">SignUp</span><span class="o">></span> <span class="o">{</span>
<span class="n">TextEditingController</span> <span class="n">controller</span> <span class="o">=</span> <span class="n">TextEditingController</span><span class="o">(</span><span class="nl">text:</span> <span class="s">'initValue'</span><span class="o">);</span>
<span class="nd">@override</span>
<span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Scaffold</span><span class="o">(</span>
<span class="nl">appBar:</span> <span class="n">AppBar</span><span class="o">(</span><span class="nl">title:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'SignUp'</span><span class="o">)),</span>
<span class="nl">body:</span> <span class="n">Container</span><span class="o">(</span>
<span class="nl">padding:</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="o">(</span><span class="mi">10</span><span class="o">),</span>
<span class="nl">child:</span> <span class="n">Column</span><span class="o">(</span>
<span class="nl">children:</span> <span class="o"><</span><span class="n">Widget</span><span class="o">>[</span>
<span class="n">TextFormField</span><span class="o">(</span>
<span class="nl">controller:</span> <span class="n">controller</span><span class="o">,</span>
<span class="nl">onChanged:</span> <span class="o">(</span><span class="n">v</span><span class="o">)</span> <span class="o">=></span> <span class="n">print</span><span class="o">(</span><span class="s">'onChanged </span><span class="si">$v</span><span class="s">'</span><span class="o">),</span>
<span class="o">),</span>
<span class="n">RaisedButton</span><span class="o">(</span>
<span class="nl">child:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'submit!'</span><span class="o">),</span>
<span class="nl">onPressed:</span> <span class="o">()</span> <span class="o">{</span>
<span class="n">print</span><span class="o">(</span><span class="n">controller</span><span class="o">.</span><span class="na">text</span><span class="o">);</span>
<span class="n">controller</span><span class="o">.</span><span class="na">clear</span><span class="o">();</span>
<span class="o">},</span>
<span class="o">),</span>
<span class="o">],</span>
<span class="o">),</span>
<span class="o">),</span>
<span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>콘트롤러를 사용해서 위와 똑같은 동작을 하게 만들었습니다.</p>
<p>초기값을 줄 수도 있으며 값을 취하기도, 값을 클리어하기도 간단해집니다.</p>
<p>선언이 귀찮지만 결국 콘트롤러를 사용하는 것이 훨씬 편리한 것을 알 수 있습니다.</p>
<h2 id="콘트롤러-리스너">콘트롤러 리스너</h2>
<p>위의 코드의 onChanged에서 입력값을 볼 수도 있지만 리스너를 둘 수도 있습니다.</p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@override</span>
<span class="kt">void</span> <span class="nf">initState</span><span class="p">(</span><span class="o">)</span> <span class="o">{</span>
<span class="n">controller</span><span class="o">.</span><span class="na">addListener</span><span class="o">(()</span> <span class="o">=></span> <span class="n">print</span><span class="o">(</span><span class="s">'addListener: </span><span class="si">${controller.text}</span><span class="s">'</span><span class="o">));</span>
<span class="k">super</span><span class="o">.</span><span class="na">initState</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>
<p>큰 의미는 없지만 아래 코드를 설명하기 위해서 구현해본 것입니다.</p>
<h2 id="콘트롤러-해제">콘트롤러 해제</h2>
<p>리스너가 있다는 것은 콘트롤러를 생성했다면 해제해주는 것을 잊지 말아야한다는 것입니다.</p>
<p>addListener를 지정하던 안하던 해제해주는 것이 좋습니다.</p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@override</span>
<span class="kt">void</span> <span class="nf">dispose</span><span class="p">(</span><span class="o">)</span> <span class="o">{</span>
<span class="n">controller</span><span class="o">.</span><span class="na">dispose</span><span class="o">();</span>
<span class="k">super</span><span class="o">.</span><span class="na">dispose</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div></div>
<p>statuful widget에 들어올 때 initState라면 나갈 때 dispose라는 곳에 들리게 할 수 있습니다. 이때 제거하면 됩니다.</p>
<h1 id="폼검사하기">폼검사하기</h1>
<p>이메일이 비어있거나 규칙이 맞지 않을 때 유효성 판단할 수 있는 방법을 제공합니다.</p>
<p>필요한 것은 3가지 입니다.</p>
<ul>
<li>Form</li>
<li>key</li>
<li>Validator</li>
</ul>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">_SignUpState</span> <span class="kd">extends</span> <span class="n">State</span><span class="o"><</span><span class="n">SignUp</span><span class="o">></span> <span class="o">{</span>
<span class="n">TextEditingController</span> <span class="n">controller</span> <span class="o">=</span> <span class="n">TextEditingController</span><span class="o">(</span><span class="nl">text:</span> <span class="s">''</span><span class="o">);</span>
<span class="kd">final</span> <span class="n">_formKey</span> <span class="o">=</span> <span class="n">GlobalKey</span><span class="o"><</span><span class="n">FormState</span><span class="o">>();</span>
<span class="nd">@override</span>
<span class="kt">void</span> <span class="n">initState</span><span class="o">()</span> <span class="o">{</span>
<span class="n">controller</span><span class="o">.</span><span class="na">addListener</span><span class="o">(()</span> <span class="o">=></span> <span class="n">print</span><span class="o">(</span><span class="s">'addListener: </span><span class="si">${controller.text}</span><span class="s">'</span><span class="o">));</span>
<span class="k">super</span><span class="o">.</span><span class="na">initState</span><span class="o">();</span>
<span class="o">}</span>
<span class="nd">@override</span>
<span class="kt">void</span> <span class="n">dispose</span><span class="o">()</span> <span class="o">{</span>
<span class="n">controller</span><span class="o">.</span><span class="na">dispose</span><span class="o">();</span>
<span class="k">super</span><span class="o">.</span><span class="na">dispose</span><span class="o">();</span>
<span class="o">}</span>
<span class="n">_buildForm</span> <span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Form</span><span class="o">(</span>
<span class="nl">key:</span> <span class="n">_formKey</span><span class="o">,</span>
<span class="nl">child:</span> <span class="n">Container</span><span class="o">(</span>
<span class="nl">padding:</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="o">(</span><span class="mi">10</span><span class="o">),</span>
<span class="nl">child:</span> <span class="n">Column</span><span class="o">(</span>
<span class="nl">children:</span> <span class="o"><</span><span class="n">Widget</span><span class="o">>[</span>
<span class="n">TextFormField</span><span class="o">(</span>
<span class="nl">controller:</span> <span class="n">controller</span><span class="o">,</span>
<span class="nl">validator:</span> <span class="o">(</span><span class="n">v</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">v</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">)</span> <span class="k">return</span> <span class="s">'bad'</span><span class="o">;</span>
<span class="k">return</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">},</span>
<span class="o">),</span>
<span class="n">RaisedButton</span><span class="o">(</span>
<span class="nl">child:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'submit!'</span><span class="o">),</span>
<span class="nl">onPressed:</span> <span class="o">()</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">_formKey</span><span class="o">.</span><span class="na">currentState</span><span class="o">.</span><span class="na">validate</span><span class="o">())</span> <span class="k">return</span><span class="o">;</span>
<span class="n">print</span><span class="o">(</span><span class="n">controller</span><span class="o">.</span><span class="na">text</span><span class="o">);</span>
<span class="n">controller</span><span class="o">.</span><span class="na">clear</span><span class="o">();</span>
<span class="o">},</span>
<span class="o">)</span>
<span class="o">],</span>
<span class="o">),</span>
<span class="o">),</span>
<span class="o">);</span>
<span class="o">}</span>
<span class="nd">@override</span>
<span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Scaffold</span><span class="o">(</span>
<span class="nl">appBar:</span> <span class="n">AppBar</span><span class="o">(</span><span class="nl">title:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'SignUp'</span><span class="o">)),</span>
<span class="nl">body:</span> <span class="n">_buildForm</span><span class="o">(),</span>
<span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>Form으로 감싸면 코드가 길어져서 _buildForm()으로 정리했습니다.</p>
<p>_formKey.currentState.validate()를 호출하면 TextFormField에 있는 validator가 작동합니다.</p>
<p>validator는 현재 값을 인자로 주고 반환 값(return ‘bad’) 이 있다면 붉은 글씨로 텍스트폼필드 아래에 표시합니다.</p>
<h1 id="영상">영상</h1>
<!-- Courtesy of embedresponsively.com //-->
<div class="responsive-video-container">
<iframe src="https://www.youtube-nocookie.com/embed/LC5l0sepWhQ" frameborder="0" allowfullscreen=""></iframe>
</div>memifkkmemi@gmail.comTextFormInputField를 사용해서 입력부를 구현해봅니다.Flutter와 Firebase로 Android iOS 둘 다 만들기 17 router 실제 페이지에 적용하기2020-03-17T00:00:00+00:002020-03-17T00:00:00+00:00https://fkkmemi.github.io/ff/ff%20017<p>지금까지 얻은 지식을 토대로 실제 만들어야할 대상을 라우터를 통해서 전환해봅니다.</p>
<h1 id="개요">개요</h1>
<p>먼저 간단한 계획을 그려봤습니다.</p>
<p><img src="/images/ff/2020-03-16_20.26.45.png" alt="alt plan" /></p>
<ul>
<li>스플래시 페이지 3초</li>
<li>인증 대기페이지(라우팅 역할)</li>
<li>인증되어 있으면 메인페이지와 프로필페이지 로그아웃</li>
<li>인증되어 있지 않으면 로그인페이지와 회원가입페이지</li>
<li>로그인 후에 바로 메인으로 가지 않고 인증체크 페이지로 이동</li>
</ul>
<blockquote>
<p>지난번처럼 StreamBuilder로 처리하기에는 Splash 페이지에 너무 많은 코드가 뒤죽박죽이 될 것 같기 때문에.. 역할별로 조금씩 나눠봅니다.</p>
</blockquote>
<blockquote>
<p>사실 하기 나름인데 실무에 적용해보니 이메일 인증 체크등 거추장스러운 것들이 많이 있었기 때문입니다.</p>
</blockquote>
<h1 id="파일-정리">파일 정리</h1>
<ul>
<li>lib/
<ul>
<li>main.dart</li>
<li>pages/
<ul>
<li>splash.dart</li>
<li>auth.dart</li>
<li>signin.dart</li>
<li>signup.dart</li>
<li>home.dart</li>
<li>profile.dart</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>위의 그림처럼 6개의 페이지를 분리해서 만들었습니다.</p>
<h1 id="maindart">main.dart</h1>
<p>라우팅 정의를 합니다.</p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>
<span class="kn">import</span> <span class="s">'pages/splash.dart'</span><span class="o">;</span>
<span class="kn">import</span> <span class="s">'pages/auth.dart'</span><span class="o">;</span>
<span class="kn">import</span> <span class="s">'pages/signin.dart'</span><span class="o">;</span>
<span class="kn">import</span> <span class="s">'pages/signup.dart'</span><span class="o">;</span>
<span class="kn">import</span> <span class="s">'pages/home.dart'</span><span class="o">;</span>
<span class="kn">import</span> <span class="s">'pages/profile.dart'</span><span class="o">;</span>
<span class="kt">void</span> <span class="nf">main</span><span class="p">(</span><span class="o">)</span> <span class="o">=></span> <span class="n">runApp</span><span class="o">(</span><span class="n">FFApp</span><span class="o">());</span>
<span class="kd">class</span> <span class="nc">FFApp</span> <span class="kd">extends</span> <span class="n">StatelessWidget</span> <span class="o">{</span>
<span class="nd">@override</span>
<span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">MaterialApp</span><span class="o">(</span>
<span class="nl">title:</span> <span class="s">'Flutter Demo'</span><span class="o">,</span>
<span class="nl">theme:</span> <span class="n">ThemeData</span><span class="o">(</span>
<span class="nl">primarySwatch:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">indigo</span><span class="o">,</span>
<span class="o">),</span>
<span class="nl">initialRoute:</span> <span class="n">SplashPage</span><span class="o">.</span><span class="na">routeName</span><span class="o">,</span>
<span class="nl">routes:</span> <span class="o">{</span>
<span class="n">SplashPage</span><span class="o">.</span><span class="na">routeName</span><span class="o">:</span> <span class="o">(</span><span class="n">context</span><span class="o">)</span> <span class="o">=></span> <span class="n">SplashPage</span><span class="o">(),</span>
<span class="n">AuthPage</span><span class="o">.</span><span class="na">routeName</span><span class="o">:</span> <span class="o">(</span><span class="n">context</span><span class="o">)</span> <span class="o">=></span> <span class="n">AuthPage</span><span class="o">(),</span>
<span class="n">SignInPage</span><span class="o">.</span><span class="na">routeName</span><span class="o">:</span> <span class="o">(</span><span class="n">context</span><span class="o">)</span> <span class="o">=></span> <span class="n">SignInPage</span><span class="o">(),</span>
<span class="n">SignUpPage</span><span class="o">.</span><span class="na">routeName</span><span class="o">:</span> <span class="o">(</span><span class="n">context</span><span class="o">)</span> <span class="o">=></span> <span class="n">SignUpPage</span><span class="o">(),</span>
<span class="n">HomePage</span><span class="o">.</span><span class="na">routeName</span><span class="o">:</span> <span class="o">(</span><span class="n">context</span><span class="o">)</span> <span class="o">=></span> <span class="n">HomePage</span><span class="o">(),</span>
<span class="n">ProfilePage</span><span class="o">.</span><span class="na">routeName</span><span class="o">:</span> <span class="o">(</span><span class="n">context</span><span class="o">)</span> <span class="o">=></span> <span class="n">ProfilePage</span><span class="o">(),</span>
<span class="o">},</span>
<span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>각 파일에는 routeName이 정의되어 있기 때문에 x.routeName 형태로 작성했습니다.</p>
<h1 id="splashdart">splash.dart</h1>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>
<span class="kn">import</span> <span class="s">'dart:async'</span><span class="o">;</span>
<span class="kd">class</span> <span class="nc">SplashPage</span> <span class="kd">extends</span> <span class="n">StatefulWidget</span> <span class="o">{</span>
<span class="n">SplashPage</span><span class="o">({</span><span class="n">Key</span> <span class="n">key</span><span class="o">})</span> <span class="o">:</span> <span class="k">super</span><span class="o">(</span><span class="nl">key:</span> <span class="n">key</span><span class="o">);</span>
<span class="kd">static</span> <span class="kd">const</span> <span class="n">routeName</span> <span class="o">=</span> <span class="s">'/'</span><span class="o">;</span>
<span class="nd">@override</span>
<span class="n">_SplashPageState</span> <span class="n">createState</span><span class="o">()</span> <span class="o">=></span> <span class="n">_SplashPageState</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">class</span> <span class="nc">_SplashPageState</span> <span class="kd">extends</span> <span class="n">State</span><span class="o"><</span><span class="n">SplashPage</span><span class="o">></span> <span class="o">{</span>
<span class="nd">@override</span>
<span class="kt">void</span> <span class="n">initState</span><span class="o">()</span> <span class="o">{</span>
<span class="k">super</span><span class="o">.</span><span class="na">initState</span><span class="o">();</span>
<span class="n">_routePage</span><span class="o">();</span>
<span class="o">}</span>
<span class="n">_routePage</span> <span class="o">()</span> <span class="n">async</span> <span class="o">{</span>
<span class="n">await</span> <span class="n">Future</span><span class="o">.</span><span class="na">delayed</span><span class="o">(</span><span class="n">Duration</span><span class="o">(</span><span class="nl">seconds:</span> <span class="mi">4</span><span class="o">));</span>
<span class="k">return</span> <span class="n">Navigator</span><span class="o">.</span><span class="na">pushReplacementNamed</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="s">'/auth'</span><span class="o">);</span>
<span class="o">}</span>
<span class="n">Widget</span> <span class="n">_buildBody</span><span class="o">(</span><span class="n">message</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Scaffold</span><span class="o">(</span>
<span class="nl">body:</span> <span class="n">Container</span><span class="o">(</span>
<span class="nl">color:</span> <span class="n">Theme</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="n">context</span><span class="o">).</span><span class="na">primaryColor</span><span class="o">,</span>
<span class="nl">padding:</span> <span class="n">EdgeInsets</span><span class="o">.</span><span class="na">all</span><span class="o">(</span><span class="mi">10</span><span class="o">),</span>
<span class="nl">child:</span> <span class="n">Center</span><span class="o">(</span>
<span class="nl">child:</span> <span class="n">Column</span><span class="o">(</span>
<span class="nl">mainAxisAlignment:</span> <span class="n">MainAxisAlignment</span><span class="o">.</span><span class="na">spaceAround</span><span class="o">,</span>
<span class="nl">children:</span> <span class="o"><</span><span class="n">Widget</span><span class="o">>[</span>
<span class="n">Image</span><span class="o">.</span><span class="na">asset</span><span class="o">(</span><span class="s">'assets/icon.png'</span><span class="o">,</span> <span class="nl">width:</span> <span class="mi">200</span><span class="o">),</span>
<span class="n">Text</span><span class="o">(</span><span class="n">message</span><span class="o">,</span> <span class="nl">style:</span> <span class="n">Theme</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="n">context</span><span class="o">).</span><span class="na">textTheme</span><span class="o">.</span><span class="na">body1</span><span class="o">.</span><span class="na">copyWith</span><span class="o">(</span><span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">white</span><span class="o">),),</span>
<span class="n">Text</span><span class="o">(</span><span class="s">'Copyright © fkkmemi.'</span><span class="o">,</span> <span class="nl">style:</span> <span class="n">Theme</span><span class="o">.</span><span class="na">of</span><span class="o">(</span><span class="n">context</span><span class="o">).</span><span class="na">textTheme</span><span class="o">.</span><span class="na">caption</span><span class="o">.</span><span class="na">copyWith</span><span class="o">(</span><span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">white</span><span class="o">),)</span>
<span class="o">],</span>
<span class="o">),</span>
<span class="o">),</span>
<span class="o">),</span>
<span class="o">);</span>
<span class="o">}</span>
<span class="nd">@override</span>
<span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">_buildBody</span><span class="o">(</span><span class="s">'Internet checking...'</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<blockquote>
<p>코드만 괜히 길 뿐.. 4초후 /auth페이지로 이동합니다.</p>
</blockquote>
<h1 id="authdart">auth.dart</h1>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>
<span class="kn">import</span> <span class="s">'package:firebase_auth/firebase_auth.dart'</span><span class="o">;</span>
<span class="kn">import</span> <span class="s">'dart:async'</span><span class="o">;</span>
<span class="kd">class</span> <span class="nc">AuthPage</span> <span class="kd">extends</span> <span class="n">StatefulWidget</span> <span class="o">{</span>
<span class="n">AuthPage</span><span class="o">({</span><span class="n">Key</span> <span class="n">key</span><span class="o">})</span> <span class="o">:</span> <span class="k">super</span><span class="o">(</span><span class="nl">key:</span> <span class="n">key</span><span class="o">);</span>
<span class="kd">static</span> <span class="kd">const</span> <span class="n">routeName</span> <span class="o">=</span> <span class="s">'/auth'</span><span class="o">;</span>
<span class="nd">@override</span>
<span class="n">_AuthPageState</span> <span class="n">createState</span><span class="o">()</span> <span class="o">=></span> <span class="n">_AuthPageState</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">class</span> <span class="nc">_AuthPageState</span> <span class="kd">extends</span> <span class="n">State</span><span class="o"><</span><span class="n">AuthPage</span><span class="o">></span> <span class="o">{</span>
<span class="n">StreamSubscription</span><span class="o"><</span><span class="n">FirebaseUser</span><span class="o">></span> <span class="n">_subscriptionAuth</span><span class="o">;</span>
<span class="kt">String</span> <span class="n">_message</span> <span class="o">=</span> <span class="s">'loading...'</span><span class="o">;</span>
<span class="nd">@override</span>
<span class="kt">void</span> <span class="n">initState</span><span class="o">()</span> <span class="o">{</span>
<span class="k">super</span><span class="o">.</span><span class="na">initState</span><span class="o">();</span>
<span class="n">_streamOpen</span><span class="o">();</span>
<span class="o">}</span>
<span class="nd">@override</span>
<span class="kt">void</span> <span class="n">dispose</span><span class="o">()</span> <span class="o">{</span>
<span class="n">_subscriptionAuth</span><span class="o">.</span><span class="na">cancel</span><span class="o">();</span>
<span class="k">super</span><span class="o">.</span><span class="na">dispose</span><span class="o">();</span>
<span class="o">}</span>
<span class="n">_streamOpen</span><span class="o">()</span> <span class="o">{</span>
<span class="n">_subscriptionAuth</span> <span class="o">=</span> <span class="n">FirebaseAuth</span><span class="o">.</span><span class="na">instance</span><span class="o">.</span><span class="na">onAuthStateChanged</span><span class="o">.</span><span class="na">listen</span><span class="o">((</span><span class="n">fu</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">fu</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="k">return</span> <span class="n">Navigator</span><span class="o">.</span><span class="na">pushReplacementNamed</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="s">'/signin'</span><span class="o">);</span>
<span class="k">return</span> <span class="n">Navigator</span><span class="o">.</span><span class="na">pushReplacementNamed</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="s">'/home'</span><span class="o">);</span>
<span class="o">});</span>
<span class="o">}</span>
<span class="nd">@override</span>
<span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Scaffold</span><span class="o">(</span>
<span class="nl">appBar:</span> <span class="n">AppBar</span><span class="o">(</span>
<span class="nl">title:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'Account check'</span><span class="o">)</span>
<span class="o">),</span>
<span class="nl">body:</span> <span class="n">Container</span><span class="o">(</span>
<span class="nl">child:</span> <span class="n">Center</span><span class="o">(</span>
<span class="nl">child:</span> <span class="n">Column</span><span class="o">(</span>
<span class="nl">mainAxisAlignment:</span> <span class="n">MainAxisAlignment</span><span class="o">.</span><span class="na">center</span><span class="o">,</span>
<span class="nl">children:</span> <span class="o"><</span><span class="n">Widget</span><span class="o">>[</span>
<span class="n">Text</span><span class="o">(</span><span class="n">_message</span><span class="o">),</span>
<span class="n">CircularProgressIndicator</span><span class="o">(),</span>
<span class="o">],</span>
<span class="o">),</span>
<span class="o">),</span>
<span class="o">),</span>
<span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>시작할 때(initState) 계정 감시(스트림 리슨)를 시작합니다.</p>
<p>스트림 리슨을 할 때 구독(subscription)을 받아 놓은 이유는 나갈 때를 위함입니다.</p>
<p>리슨하고 있다가 user가 null이면 로그인 페이지로, 있으면 홈페이지로 이동시킵니다.</p>
<p>주의할 점은 페이지 나갈 때(dispose) 구독을 해지해줘야합니다.</p>
<p>구독 해지 없이 나가면 다른페이지로 라우트시 워닝메세지가 뜹니다.</p>
<blockquote>
<p>역시 코드만 길 뿐 그다지 하는 일도 없습니다.</p>
</blockquote>
<h1 id="signindart">signin.dart</h1>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>
<span class="kn">import</span> <span class="s">'package:firebase_auth/firebase_auth.dart'</span><span class="o">;</span>
<span class="kn">import</span> <span class="s">'package:google_sign_in/google_sign_in.dart'</span><span class="o">;</span>
<span class="kd">class</span> <span class="nc">SignInPage</span> <span class="kd">extends</span> <span class="n">StatefulWidget</span> <span class="o">{</span>
<span class="n">SignInPage</span><span class="o">({</span><span class="n">Key</span> <span class="n">key</span><span class="o">})</span> <span class="o">:</span> <span class="k">super</span><span class="o">(</span><span class="nl">key:</span> <span class="n">key</span><span class="o">);</span>
<span class="kd">static</span> <span class="kd">const</span> <span class="n">routeName</span> <span class="o">=</span> <span class="s">'/signin'</span><span class="o">;</span>
<span class="nd">@override</span>
<span class="n">_SignInPageState</span> <span class="n">createState</span><span class="o">()</span> <span class="o">=></span> <span class="n">_SignInPageState</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">class</span> <span class="nc">_SignInPageState</span> <span class="kd">extends</span> <span class="n">State</span><span class="o"><</span><span class="n">SignInPage</span><span class="o">></span> <span class="o">{</span>
<span class="kt">bool</span> <span class="n">_loading</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span>
<span class="n">_googleSignIn</span> <span class="o">()</span> <span class="n">async</span> <span class="o">{</span>
<span class="kd">final</span> <span class="kt">bool</span> <span class="n">isSignedIn</span> <span class="o">=</span> <span class="n">await</span> <span class="n">GoogleSignIn</span><span class="o">().</span><span class="na">isSignedIn</span><span class="o">();</span>
<span class="n">GoogleSignInAccount</span> <span class="n">googleUser</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">isSignedIn</span><span class="o">)</span> <span class="n">googleUser</span> <span class="o">=</span> <span class="n">await</span> <span class="n">GoogleSignIn</span><span class="o">().</span><span class="na">signInSilently</span><span class="o">();</span>
<span class="k">else</span> <span class="n">googleUser</span> <span class="o">=</span> <span class="n">await</span> <span class="n">GoogleSignIn</span><span class="o">().</span><span class="na">signIn</span><span class="o">();</span>
<span class="kd">final</span> <span class="n">GoogleSignInAuthentication</span> <span class="n">googleAuth</span> <span class="o">=</span> <span class="n">await</span> <span class="n">googleUser</span><span class="o">.</span><span class="na">authentication</span><span class="o">;</span>
<span class="kd">final</span> <span class="n">AuthCredential</span> <span class="n">credential</span> <span class="o">=</span> <span class="n">GoogleAuthProvider</span><span class="o">.</span><span class="na">getCredential</span><span class="o">(</span>
<span class="nl">accessToken:</span> <span class="n">googleAuth</span><span class="o">.</span><span class="na">accessToken</span><span class="o">,</span>
<span class="nl">idToken:</span> <span class="n">googleAuth</span><span class="o">.</span><span class="na">idToken</span><span class="o">,</span>
<span class="o">);</span>
<span class="kd">final</span> <span class="n">FirebaseUser</span> <span class="n">user</span> <span class="o">=</span> <span class="o">(</span><span class="n">await</span> <span class="n">FirebaseAuth</span><span class="o">.</span><span class="na">instance</span><span class="o">.</span><span class="na">signInWithCredential</span><span class="o">(</span><span class="n">credential</span><span class="o">)).</span><span class="na">user</span><span class="o">;</span>
<span class="c1">// print("signed in " + user.displayName);</span>
<span class="k">return</span> <span class="n">user</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">_buildLoading</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Center</span><span class="o">(</span><span class="nl">child:</span> <span class="n">CircularProgressIndicator</span><span class="o">(),);</span>
<span class="o">}</span>
<span class="n">_buildBody</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Column</span><span class="o">(</span>
<span class="nl">children:</span> <span class="o"><</span><span class="n">Widget</span><span class="o">>[</span>
<span class="n">RaisedButton</span><span class="o">(</span>
<span class="nl">child:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'google login'</span><span class="o">),</span>
<span class="nl">onPressed:</span> <span class="o">()</span> <span class="n">async</span> <span class="o">{</span>
<span class="n">setState</span><span class="o">(()</span> <span class="o">=></span> <span class="n">_loading</span> <span class="o">=</span> <span class="kc">true</span><span class="o">);</span>
<span class="n">await</span> <span class="n">_googleSignIn</span><span class="o">();</span>
<span class="n">setState</span><span class="o">(()</span> <span class="o">=></span> <span class="n">_loading</span> <span class="o">=</span> <span class="kc">false</span><span class="o">);</span>
<span class="c1">// Navigator.pushReplacementNamed(context, '/');</span>
<span class="c1">// Navigator.pushReplacementNamed(context, '/auth');</span>
<span class="n">Navigator</span><span class="o">.</span><span class="na">pushReplacementNamed</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="s">'/home'</span><span class="o">);</span>
<span class="o">},</span>
<span class="o">),</span>
<span class="n">RaisedButton</span><span class="o">(</span>
<span class="nl">child:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'register'</span><span class="o">),</span>
<span class="nl">onPressed:</span> <span class="o">()</span> <span class="o">{</span>
<span class="n">Navigator</span><span class="o">.</span><span class="na">pushNamed</span><span class="o">(</span><span class="n">context</span><span class="o">,</span> <span class="s">'/signup'</span><span class="o">);</span>
<span class="o">},</span>
<span class="o">),</span>
<span class="o">],</span>
<span class="o">);</span>
<span class="o">}</span>
<span class="nd">@override</span>
<span class="n">Widget</span> <span class="n">build</span><span class="o">(</span><span class="n">BuildContext</span> <span class="n">context</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">Scaffold</span><span class="o">(</span>
<span class="nl">appBar:</span> <span class="n">AppBar</span><span class="o">(</span><span class="nl">title:</span> <span class="n">Text</span><span class="o">(</span><span class="s">'SignInPage'</span><span class="o">),</span> <span class="o">),</span>
<span class="nl">body:</span> <span class="n">_loading</span> <span class="o">?</span> <span class="n">_buildLoading</span><span class="o">()</span> <span class="o">:</span> <span class="n">_buildBody</span><span class="o">()</span>
<span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>예전에 썼던 구글로그인 코드에 이미 로그인이 되어있을 경우 조용히 로그인하는 기능만 조금 추가해봤습니다.</p>
<p>로그인이 정상적으로 된 경우 현재는 /, /auth, /home 모두 결국 /home으로 가게 됩니다.</p>
<p>/의 경우 광고차.. 스플래시를 한번 더 보여줄 수도 있습니다.
/auth의 경우 이메일 혹은 권한 관련해서 판단을 받아야할 수 있습니다.</p>
<h1 id="마치며">마치며</h1>
<p>모양은 별로지만 미리 구성을 간단히 해놓으면 추후 각 페이지만 좀 더 근사하게 포장하면 됩니다.</p>
<blockquote>
<p>사실 웹도 똑같이 빈페이지 만들어 놓고 작업합니다.</p>
</blockquote>
<p>현재 소스의 로그인 시나리오는 완벽하지 않을 것입니다.</p>
<p>플러터 관련 게시물들을 둘러봐도 로그인 시나리오는 너무나도 다양합니다.</p>
<p>10년간 많이도 다양한 플랫폼(웹, 임베디드, 모바일등)에서 여러가지 형태로 구현해본들 만족한 적은 단 한번도 없었던 것 같습니다.</p>
<p>과거에 구현해 놓은 시나리오는 도자기를 깨는 것 처럼 수천번은 갈아 엎었던 것 같습니다.</p>
<blockquote>
<p>그런 의미에서 과거 강의에 있던 로그인기능은 참고만 하시고 개선해서 사용하시기 바랍니다. 저는 다 바꿨답니다..(바뀐 내용은 강의 리뉴얼 때..)</p>
</blockquote>
<p>절대 자신의 코드를 과신하지 말고, 자신도 모르게 만들어 놓은 복잡하고 의미없는 규칙에 얽히고 있지는 않은 지.. 항상 돌아보는 것이 좋습니다.</p>
<p>굵직한 흐름을 읽어야 도자기를 부수고 다시 구울 수 있습니다.</p>
<p>개발자라면 항상 완벽하진 못해도 완벽해지기 위한 노력이 더 중요한 것이라고 생각합니다.</p>
<h1 id="소스">소스</h1>
<p>이제부터 강의 코드가 길어질 것 같아서 깃헙에 등록해두었습니다.</p>
<p><a href="https://github.com/fkkmemi/ff2">https://github.com/fkkmemi/ff2</a></p>
<h1 id="영상">영상</h1>
<!-- Courtesy of embedresponsively.com //-->
<div class="responsive-video-container">
<iframe src="https://www.youtube-nocookie.com/embed/UR_eP7Wd1_c" frameborder="0" allowfullscreen=""></iframe>
</div>memifkkmemi@gmail.com지금까지 얻은 지식을 토대로 실제 만들어야할 대상을 라우터를 통해서 전환해봅니다.