tag:blogger.com,1999:blog-376291992024-02-07T23:05:20.359+09:00M.I.のプログラミング・メモJavaScript, React, Android, iOS...などなどMakotohttp://www.blogger.com/profile/09442687702666423250noreply@blogger.comBlogger203125tag:blogger.com,1999:blog-37629199.post-28628226903147353352023-02-06T19:29:00.010+09:002023-11-21T15:29:37.540+09:00左右分割型エルゴノミクス・キーボード 6機種の検討 - 2023年2月<h3 style="text-align: left;">方針</h3><div><ul style="text-align: left;"><li>金に糸目はつけない方向でいきます。自己満足度優先。</li><li>中央部を高くして手首の負担を減らすことが出来ること。</li></ul></div><div><br /></div><div><br /></div><h3 style="text-align: left;"><a href="https://www.zsa.io/moonlander" target="_blank">Moonlander MARK I</a></h3><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSKvY6Pm00laaMk0B4iKs5eFIwIW3gkLf-TL3z5ufJetusf68SakXO58PsmAMQi6gIMFfk3xfuox52KmLcud7AmqBEHuxyAyJMNmZX7RcuQ-UR50AIWFpsxZntrgIZ0f1PMndCWmLI2aUOiNdYDlXSwi8HZltJrQw4OfNUTpPNVO-fhTvjMA/s2550/Screenshot%202023-02-05%20at%2011.23.57%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1146" data-original-width="2550" height="144" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSKvY6Pm00laaMk0B4iKs5eFIwIW3gkLf-TL3z5ufJetusf68SakXO58PsmAMQi6gIMFfk3xfuox52KmLcud7AmqBEHuxyAyJMNmZX7RcuQ-UR50AIWFpsxZntrgIZ0f1PMndCWmLI2aUOiNdYDlXSwi8HZltJrQw4OfNUTpPNVO-fhTvjMA/s320/Screenshot%202023-02-05%20at%2011.23.57%20PM.png" width="320" /></a></div><br /><div>とにかく見た目がかっこいい。惚れてしまう。</div><div><br /></div><div>ただ、キーの配列が縦に揃っているので慣れるまで時間がかかりそうなのが気がかり。若かりし頃ならすぐに慣れたかも知れないけれど、歳を取るとなかなか過去のしがらみを捨てるのは難しいもの。</div><div><br /></div><div><br /></div><div></div><blockquote><div>(参考)📔 Moonlander というエルゴノミクスキーボードのススメ | Nikaeraintokyo. </div><div><a href="https://nikaera.com/archives/introduction-to-moonlander/" target="_blank">https://nikaera.com/archives/introduction-to-moonlander/</a></div></blockquote><div><a href="https://nikaera.com/archives/introduction-to-moonlander/" target="_blank"></a></div><div><br /></div><div><br /></div><div><br /></div><h3 style="text-align: left;"><a href="https://ergodox-ez.com/" target="_blank">Ergodox EZ</a></h3><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgj92Ax3LNg6r5tsNQUsHdm8Ye5bVnl9n5YMMTU4eEIt2Cq1qdWpagmIKe9RtAJk10qxMnmDVjHArHaeSyOTyquOQ9yqk3Xu4Jxp_aQxlGRL6RTsZYltp-bThKoFzPFtk5pyLihUihxripucCiWi0ZCIxecR6Sk9mZ6YqQlowh3Qx3zcOKNnw/s2474/Screenshot%202023-02-05%20at%2011.25.33%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1316" data-original-width="2474" height="170" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgj92Ax3LNg6r5tsNQUsHdm8Ye5bVnl9n5YMMTU4eEIt2Cq1qdWpagmIKe9RtAJk10qxMnmDVjHArHaeSyOTyquOQ9yqk3Xu4Jxp_aQxlGRL6RTsZYltp-bThKoFzPFtk5pyLihUihxripucCiWi0ZCIxecR6Sk9mZ6YqQlowh3Qx3zcOKNnw/s320/Screenshot%202023-02-05%20at%2011.25.33%20PM.png" width="320" /></a></div><br /><div>こちらもいいけどMoonlanderの方が新しいみたいなのでこれを選ぶならMoonlanderにするかな。</div><div><br /></div><div><br /></div><div><blockquote>(参考)【2021年度版】もう一度ErgoDox EZを設定する - zuckey blog <a href="https://blog.zuckey17.org/entry/ergodox-ez-2021" target="_blank">https://blog.zuckey17.org/entry/ergodox-ez-2021</a></blockquote><a href="https://blog.zuckey17.org/entry/ergodox-ez-2021" target="_blank"></a></div><div><br /></div><div><br /></div><div><br /></div><h3 style="text-align: left;"><a href="https://kinesis-ergo.com/keyboards/freestyle2-keyboard/" target="_blank">KINESIS Freestyle2</a> </h3><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRYlegYepr3ydtgBIS3DCY8rwzSqSByOzQf45ffWjZVN3i7V7dziR5g-ORiJAtZF_muiB0lZ2rS9bGM45buO8ILtCDc4xuXElZ3kZ6nzOyqeH126Ckzq7_ilRhQHF766BelSuFZ-7eDSSa0IPwMelxyoqNcXYpeO5D6kS5aq28twEBR5mEvA/s4800/kb820-landscape.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1800" data-original-width="4800" height="120" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRYlegYepr3ydtgBIS3DCY8rwzSqSByOzQf45ffWjZVN3i7V7dziR5g-ORiJAtZF_muiB0lZ2rS9bGM45buO8ILtCDc4xuXElZ3kZ6nzOyqeH126Ckzq7_ilRhQHF766BelSuFZ-7eDSSa0IPwMelxyoqNcXYpeO5D6kS5aq28twEBR5mEvA/s320/kb820-landscape.jpg" width="320" /></a></div><div><br /></div><div>悪くなさそう。財布にも優しいので、とりあえず分割型を試すならこれがいいかも。</div><div><br /></div><div>ESCキーが大きくてFunctionキーもある。矢印キーもあるのは正直嬉しい。今使っているRealforceから移行するのには最も違和感が少なそう。</div><div><br /></div><div>よく見たらCopy/Paste専用キーなんていうのまである。</div><div><br /></div><div><blockquote>(参考)Kinesis FreeStyle2を購入した - repl.info <a href="https://repl.info/archives/701/" target="_blank">https://repl.info/archives/701/</a></blockquote><a href="https://repl.info/archives/701/" target="_blank"></a></div><div><br /></div><div><br /></div><h3 style="text-align: left;"><a href="https://kinesis-ergo.com/keyboards/freestyle-pro-keyboard/" target="_blank">KINESIS Freestyle Pro</a></h3><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvzDNLMGlSpCf0MgbdqkyYb1V224_cwFvWd5I3QqoS4kJjdceq8hdd7XIZcL1N8-D7egw1NYiamWybxJOY-TL7X0b7iuDTLE9gyGea3zY4va7sgyMnto6PkUHSj170-cS4rFnysM_PRBmsphmabKxe3xMdJHj4OmA-QKf9bS0zKJg9_rcjpA/s276/Long-Form-Pro-Features-Tent-276x186.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="186" data-original-width="276" height="186" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvzDNLMGlSpCf0MgbdqkyYb1V224_cwFvWd5I3QqoS4kJjdceq8hdd7XIZcL1N8-D7egw1NYiamWybxJOY-TL7X0b7iuDTLE9gyGea3zY4va7sgyMnto6PkUHSj170-cS4rFnysM_PRBmsphmabKxe3xMdJHj4OmA-QKf9bS0zKJg9_rcjpA/s1600/Long-Form-Pro-Features-Tent-276x186.jpg" width="276" /></a></div><br /><div>こちらも悪くない。キーの配置と数はFreestyle2と同じみたい。スイッチがメカニカル式なのでKINESISを選ぶならこちらの方が良いのかな。キーのリマップなどカスタマイズ性もこちらの方が高いみたい。</div><div><br /></div><div><br /></div><div><br /></div><h3 style="text-align: left;"><a href="https://dygma.com/pages/raise" target="_blank">Dygma Raise</a></h3><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwKHx83sCTMrF9aGpFDydDeDbK2K7tRAaM0NN5vAzFbTRUFjmHfwJ6rnVjyeGn6-Gor3qX1TazTLTFod5nQGPnY1GMYSbH2p__WMwmh7oi-3CKba6n5Fb_jia9R9YJhabn_Kl_2-kpMuWNZBHqxrmvdoZ7Y8X1xzQHDxWKzg3c0p0mldEsrw/s2998/Screenshot%202023-02-05%20at%2011.34.26%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1302" data-original-width="2998" height="139" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwKHx83sCTMrF9aGpFDydDeDbK2K7tRAaM0NN5vAzFbTRUFjmHfwJ6rnVjyeGn6-Gor3qX1TazTLTFod5nQGPnY1GMYSbH2p__WMwmh7oi-3CKba6n5Fb_jia9R9YJhabn_Kl_2-kpMuWNZBHqxrmvdoZ7Y8X1xzQHDxWKzg3c0p0mldEsrw/s320/Screenshot%202023-02-05%20at%2011.34.26%20PM.png" width="320" /></a></div><br /><div>しびれるデザイン。Youtubeなどでは "Gaming Keyboard" として紹介されている。</div><div><br /></div><div>ESCキーが1キーの左にある。その代わりに消えた「~」と「`」はレイヤーを切り替えて打つのかな?</div><div><br /></div><div>親指で押せるキーが8個に分かれているのが便利そうでかなりそそられる。</div><div><br /></div><div><br /></div><div></div><blockquote><div>(参考)分離式メカニカルキーボードDygma Raiseレビュー!気になる使用感は? </div><div><a href="https://de-advice.com/dygma-raise-review" target="_blank">https://de-advice.com/dygma-raise-review</a></div></blockquote><div><a href="https://de-advice.com/dygma-raise-review" target="_blank"></a></div><div><br /></div><div><br /></div><div><br /></div><h3 style="text-align: left;"><a href="https://ultimatehackingkeyboard.com/" target="_blank">Ultimate Hacking Keyboard</a></h3><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgeNfil5p5ltxBo-RB644X279sNc58KWlVFfqSO-sla0QjB78C0LYLqRMdKMGUwE9DSH5nvHJ0O_Q1EbaXQylOFi6dn27eH7DcHjgw9bu53FmX8l_qWagD9uV326WBkcSc-dXD5alhDVR82u782aw_QyP8uujmAtqUmhOwYRcMeLK6wg09Y1w/s2540/Screenshot%202023-02-05%20at%2011.53.13%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1348" data-original-width="2540" height="170" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgeNfil5p5ltxBo-RB644X279sNc58KWlVFfqSO-sla0QjB78C0LYLqRMdKMGUwE9DSH5nvHJ0O_Q1EbaXQylOFi6dn27eH7DcHjgw9bu53FmX8l_qWagD9uV326WBkcSc-dXD5alhDVR82u782aw_QyP8uujmAtqUmhOwYRcMeLK6wg09Y1w/s320/Screenshot%202023-02-05%20at%2011.53.13%20PM.png" width="320" /></a></div><br /><div><br /></div><div>レゴブロックのようなモジュールシステムで中央部にトラックボールを付けられることに感動。</div><div><br /></div><div>分割した真ん中にトラックパッドやトラックボールを置くよりも手の移動が少なくて良さそう。ボールがもう少し大きいほうが動かしやすそうかなとは思うものの、とりあえず使い勝手を試してみたい。</div><div><br /></div><div>ESCキーが見当たらないのが若干気になるのだけど、中央左側の拡張キーのどれかに割り付ければ問題ないのかな。</div><div><br /></div><div>木製のリストパッドの質感もキーボード本体のクオリティも結構高そうで期待が持てる。</div><div><br /></div><div><br /></div><div></div><blockquote><div>(参考)本日の買い物:Ultimate Hacking Keyboard </div><div><a href="https://skoji.jp/blog/2022/08/uhk.html" target="_blank">https://skoji.jp/blog/2022/08/uhk.html</a></div></blockquote><div><a href="https://skoji.jp/blog/2022/08/uhk.html" target="_blank"></a></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><h3 style="text-align: left;">結論</h3><div><br /></div><div>Ultimate Hacking Keyboard をオーダーしました。一部の部品が在庫切れとのことで、発送まで3週間待ち。のんびり待つことにします。</div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div> 🍻</div>Makotohttp://www.blogger.com/profile/09442687702666423250noreply@blogger.comtag:blogger.com,1999:blog-37629199.post-23386828184078763762023-01-15T07:55:00.003+09:002023-01-15T07:57:07.076+09:00Mac で fileproviderd のCPU使用率が100%以上で止まらなくなって困った時にやったこと<div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3RD8011dGMkjkBMbavbzgrfmHtyTSK5Tp6-SwCYqOHnOzSVmndSrq0icv2BEthCKYRIfbyZs3Esz-eUpIDmaSrwCSNrOmM9yw1lbDn8y14CkJBq8ynLsC_jL59cPAoJrEXw1EQf-N4jYbezdf4XlXe3Pqx2jXFZ7uLsN0GVbn_Mxj21W1Pg/s700/Screenshot%202023-01-14%20at%2012.46.05%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="700" data-original-width="546" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3RD8011dGMkjkBMbavbzgrfmHtyTSK5Tp6-SwCYqOHnOzSVmndSrq0icv2BEthCKYRIfbyZs3Esz-eUpIDmaSrwCSNrOmM9yw1lbDn8y14CkJBq8ynLsC_jL59cPAoJrEXw1EQf-N4jYbezdf4XlXe3Pqx2jXFZ7uLsN0GVbn_Mxj21W1Pg/s320/Screenshot%202023-01-14%20at%2012.46.05%20PM.png" width="250" /></a></div><br /><div><br /><h2 style="text-align: left;">Mac で fileproviderd のCPU使用率が100%以上で止まらなくなって困った時にやったこと。</h2></div><div><br /></div><div><br /></div><div><b>/Library/Application Support/FileProvider/ 以下のファイルをすべて削除</b></div><div><br /></div><div><br /></div><div>これで無事解決しました。</div><div><br /></div><div><br /></div><div> 参照元:</div><div> <a href="https://pnch.hatenablog.com/entry/fileprovider" target="_blank">fileproviderdのCPU暴走を解決した話 - 建築・デザイン・まちづくりを思考する - ケンチククラブ</a> </div><div><br /></div><div><br /></div><div>ありがとうございました!</div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div>以上!</div><div><br /></div>Makotohttp://www.blogger.com/profile/09442687702666423250noreply@blogger.comtag:blogger.com,1999:blog-37629199.post-55912226742109506002022-11-17T12:35:00.001+09:002022-11-17T12:35:19.700+09:00Visit Japan Web について、細かいことですが一応。<span face="Roboto, Arial, sans-serif" style="background-color: white; color: #202124; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;">帰国準備にあたってデジタル庁の <a href="https://www.vjw.digital.go.jp/" rel="nofollow" target="_blank">Visit Japan Web</a> にて検疫と税関の手続きをしました。</span><br style="background-color: white; color: #202124; font-family: Roboto, Arial, sans-serif; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;" /><br /><br /><span face="Roboto, Arial, sans-serif" style="background-color: white; color: #202124; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjX4My9a9KPRirPSBckfnHISY7fEfCJlurdq_K0QiwAvRyvL263nwHZw3l2m_z7IOF4EPGUVHEobH47JuAy5S8MwEX9uf0DZf9-pzSB8Q5vpQSDk_IxF-TmxeKGqJJozHDQP30UWCDp-2O3FOM-qHwt9sfSYySus1n3EzbDSVWXz_iyFZ98Fg/s1396/Screenshot%202022-11-16%20at%204.25.48%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1114" data-original-width="1396" height="255" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjX4My9a9KPRirPSBckfnHISY7fEfCJlurdq_K0QiwAvRyvL263nwHZw3l2m_z7IOF4EPGUVHEobH47JuAy5S8MwEX9uf0DZf9-pzSB8Q5vpQSDk_IxF-TmxeKGqJJozHDQP30UWCDp-2O3FOM-qHwt9sfSYySus1n3EzbDSVWXz_iyFZ98Fg/s320/Screenshot%202022-11-16%20at%204.25.48%20PM.png" width="320" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: center;"><br /></div>アカウントを作成し、パスポート写真とCOVIDワクチン接種カードをアップロード。おそらくパスポートは自動認識、COVIDカードは人力で確認しているっぽい感じです。とはいえそれほど待たされることもなく「審査完了」(青色)になったので、ここまでは良く作り込まれているなと感じました。</span><br style="background-color: white; color: #202124; font-family: Roboto, Arial, sans-serif; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;" /><br /><br style="background-color: white; color: #202124; font-family: Roboto, Arial, sans-serif; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;" /><span face="Roboto, Arial, sans-serif" style="background-color: white; color: #202124; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;">ただ一点残念だったのは、税関申告のページで「出発地」を入力する部分。</span><div><span face="Roboto, Arial, sans-serif" style="background-color: white; color: #202124; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;"><br /></span></div><div><span face="Roboto, Arial, sans-serif" style="background-color: white; color: #202124; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;">入力欄をクリックするとドロップダウンリストが表示されるのですが、全世界の都市名が不明な並び順でずらーっと出てきます。</span></div><div><span face="Roboto, Arial, sans-serif" style="background-color: white; color: #202124; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;"><br /></span></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfEleR23jO9zwaEU31QsRGIYyWKPvR8ecJhxv5ncrZKReZenr1MueeKGj-4N21vF4BL-OY7Jzammonj_SITQ8XwhG8tBozPOS0Ry9563tyLPBkIXyjTx9R7JLmF66V5ro3tvSPwhMyetwZxXCUlXQArc5qmfvxk1hjcHS9zTJeHMywIDnXdg/s756/Screenshot%202022-11-16%20at%204.23.33%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="756" data-original-width="660" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfEleR23jO9zwaEU31QsRGIYyWKPvR8ecJhxv5ncrZKReZenr1MueeKGj-4N21vF4BL-OY7Jzammonj_SITQ8XwhG8tBozPOS0Ry9563tyLPBkIXyjTx9R7JLmF66V5ro3tvSPwhMyetwZxXCUlXQArc5qmfvxk1hjcHS9zTJeHMywIDnXdg/w174-h200/Screenshot%202022-11-16%20at%204.23.33%20PM.png" width="174" /></a></div><br /><span face="Roboto, Arial, sans-serif" style="background-color: white; color: #202124; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;"><br /></span></div><div><span face="Roboto, Arial, sans-serif" style="background-color: white; color: #202124; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;"><br /></span></div><div><span face="Roboto, Arial, sans-serif" style="background-color: white; color: #202124; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;">ひたすらスクロールしても「ホノルル」も「ハワイ」も見つかりません。</span></div><div><span face="Roboto, Arial, sans-serif" style="background-color: white; color: #202124; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;"><br /></span></div><div><span face="Roboto, Arial, sans-serif" style="background-color: white; color: #202124; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;">さらにスクロールし続けると日本語の後になぜか英語表記が続きます。「英語なら見つかるのかな?」とさらに探しても「Honolulu」も「Hawaii」も選択肢にありません。</span></div><div><span face="Roboto, Arial, sans-serif" style="background-color: white; color: #202124; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;"><br /></span></div><div><span face="Roboto, Arial, sans-serif" style="background-color: white; color: #202124; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;">かろうじて「Other」という選択肢があったので、それにしようかとも思ったのですが、「いや、これだけホノルル便で入国する人が居るのだからまさか選択肢に無いということは無いだろう」と悩んでしまいました。</span><br style="background-color: white; color: #202124; font-family: Roboto, Arial, sans-serif; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;" /><br /><br style="background-color: white; color: #202124; font-family: Roboto, Arial, sans-serif; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;" /><span face="Roboto, Arial, sans-serif" style="background-color: white; color: #202124; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;">数秒間考えた後ふと思いついてキーボードから「H」と入力すると、ドロップダウンリストの内容が'H'で始まる都市名に絞られたので、「そうか、なるほど~」と「HONO」の4文字を入力したところでようやく「HONOLULU」の選択肢が現れました。</span><br style="background-color: white; color: #202124; font-family: Roboto, Arial, sans-serif; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;" /><br style="background-color: white; color: #202124; font-family: Roboto, Arial, sans-serif; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;" /><span face="Roboto, Arial, sans-serif" style="background-color: white; color: #202124; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;">ところがどっこい、下の画像のとおり、「HONOLULU HA」と「HONOLULU - HA」の2行が表示されています。どっちを選べば良いのでしょう。笑)<br /><br /><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSNWb4PFQU6dYwjUeV_h4fbEJk2k4wICOgIRaktYaK5hcwyaIS_o1a-OZ_AXbQ1K-arpcCwm4I355RNmy6A8UMNf5dyYVMpLdWh-OO3x-d7vPmC6aKZX_TWSY0vBe82M5xCnuhUbPq5AQ4Jna9r8GvC7a96mYFNeoCIveYs3r9N2_AwJmrSA/s816/Screenshot%202022-11-16%20at%204.23.56%20PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="478" data-original-width="816" height="187" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSNWb4PFQU6dYwjUeV_h4fbEJk2k4wICOgIRaktYaK5hcwyaIS_o1a-OZ_AXbQ1K-arpcCwm4I355RNmy6A8UMNf5dyYVMpLdWh-OO3x-d7vPmC6aKZX_TWSY0vBe82M5xCnuhUbPq5AQ4Jna9r8GvC7a96mYFNeoCIveYs3r9N2_AwJmrSA/s320/Screenshot%202022-11-16%20at%204.23.56%20PM.png" width="320" /></a></div></span><br style="background-color: white; color: #202124; font-family: Roboto, Arial, sans-serif; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;" /><br /><br style="background-color: white; color: #202124; font-family: Roboto, Arial, sans-serif; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;" /><span face="Roboto, Arial, sans-serif" style="background-color: white; color: #202124; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;">まあ多分どちらを選んでも問題は無いと思うのですが、まぎらわしい選択肢が出ることは良いことではありません。</span><br style="background-color: white; color: #202124; font-family: Roboto, Arial, sans-serif; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;" /><br /><br style="background-color: white; color: #202124; font-family: Roboto, Arial, sans-serif; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;" /><span face="Roboto, Arial, sans-serif" style="background-color: white; color: #202124; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;">まとめると、「出発地」入力欄のUIの問題点は下の3つになるかと思います。</span><br style="background-color: white; color: #202124; font-family: Roboto, Arial, sans-serif; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;" /><br /><br style="background-color: white; color: #202124; font-family: Roboto, Arial, sans-serif; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;" /><ol style="text-align: left;"><li><span face="Roboto, Arial, sans-serif" style="background-color: white; color: #202124; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;"><b> 「都市名の先頭数文字を入力すると選択肢を絞り込める」ということが見た目上分からない。</b></span></li><li><span face="Roboto, Arial, sans-serif" style="background-color: white; color: #202124; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;"><b> 何も入力せずクリックした場合に表示されるドロップダウンリストの内容が不完全。(おそらく全ての都市名を表示すると長くなり過ぎるので最初の200件程度で切っている。)</b></span></li><li><span face="Roboto, Arial, sans-serif" style="background-color: white; color: #202124; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;"><b> 選択肢の並び順が不明。また都市名に重複がある。</b></span></li></ol><br /><br style="background-color: white; color: #202124; font-family: Roboto, Arial, sans-serif; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;" /><span face="Roboto, Arial, sans-serif" style="background-color: white; color: #202124; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;">これに対して私の考える改善策は下記になります。</span><br style="background-color: white; color: #202124; font-family: Roboto, Arial, sans-serif; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;" /><br /><br style="background-color: white; color: #202124; font-family: Roboto, Arial, sans-serif; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;" /><ol style="text-align: left;"><li><b><span face="Roboto, Arial, sans-serif" style="background-color: white; color: #202124; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;"> 「出発地」入力欄の下か上に「※ 最初の2〜3文字をキー入力すると選択肢が絞り込まれます。」などの注釈を表示する。</span></b></li><li><b><span face="Roboto, Arial, sans-serif" style="background-color: white; color: #202124; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;"> 何も入力されていない場合はドロップダウンリストを表示しない。</span></b></li><li><b><span face="Roboto, Arial, sans-serif" style="background-color: white; color: #202124; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;"> 選択肢の並び順を辞書順にする。都市名の重複を除去する。</span></b></li></ol><br /><br style="background-color: white; color: #202124; font-family: Roboto, Arial, sans-serif; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;" /><span face="Roboto, Arial, sans-serif" style="background-color: white; color: #202124; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;">いずれも簡単な修正なので、近いうちに改善されることを願います。</span></div><br /><div><span face="Roboto, Arial, sans-serif" style="background-color: white; color: #202124; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;"><br /></span></div><div><span face="Roboto, Arial, sans-serif" style="background-color: white; color: #202124; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;"><b>デジタル庁の実力はまだまだこんなものではないはず。期待しております!</b></span><br style="background-color: white; color: #202124; font-family: Roboto, Arial, sans-serif; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;" /><br style="background-color: white; color: #202124; font-family: Roboto, Arial, sans-serif; font-size: 14px; font-variant-ligatures: none; letter-spacing: 0.2px; white-space: pre-wrap;" /></div><div class="separator" style="clear: both; text-align: center;"><br /></div><br /><br />Makotohttp://www.blogger.com/profile/09442687702666423250noreply@blogger.comtag:blogger.com,1999:blog-37629199.post-1677564958886363852022-02-13T18:56:00.007+09:002022-02-14T04:24:13.719+09:00MacでWindows 10/11向けにZip圧縮ファイルを作成する<div>MacのFinderで任意のフォルダを右クリックし、「圧縮」を選ぶと簡単にフォルダを丸ごとZipファイルに圧縮することが出来ます。</div><div><br /></div><div><br /></div><div>先日この方法で作成したファイルを人に送ったところ、Windows上で「フォルダ名やファイル名に日本語が含まれていると文字化けする」という問題が発生しました。</div><div><br /></div><div><br /></div><div>検索すると「MacはUTF8でWindowsはShift-JISだから...」という説明がたくさん見つかりますが、Zipファイルの解凍に関しては<a href="https://proengineer.internous.co.jp/content/columnfeature/6276" rel="nofollow" target="_blank"><b>Windows 8以降</b>であればUTF8にも対応している</a>ためOS標準の機能(エクスプローラ上で右クリックから解凍など)を使えばこの問題は起こらないはずだそうです。</div><div><br /></div><div> 参考)<a href="https://teratail.com/questions/60203" rel="nofollow" target="_blank">Windows 8以降ではutf-8形式のzipに対応するようになった、というのは本当なのでしょうか?</a> </div><div><br /></div><div><br /></div><div>ところが、念のため自分の<b>Windows 11</b>マシンで試して見たところ、エクスプローラの右クリックから解凍した場合でも確かに日本語のフォルダ名とファイル名が文字化けすることを確認しました。Windows <b>11</b>に至ってもまだこんな問題が残っているんでしょうかね。</div><div><br /></div><div><br /></div><div><br /></div><div>以下にこの問題を回避するためにとった方法をメモとして残しておきます。</div><div><br /></div><div><br /></div><div><br /></div><h2 style="text-align: left;">結論: <b>Mac用のコマンドライン版7-zipをインストールする</b>。</h2> <div>```</div><div>brew install p7zip</div><div>```</div><div><br /></div><div>自分の環境で試した限りでは、これでインストールされる 7z コマンドを使って圧縮すれば、Windows 10/11でも問題なく解凍出来るZipファイルを作成出来るようです。</div><div><br /></div><div><br /></div><h2 style="text-align: left;"><b>圧縮ファイルを作成する</b></h2><div>```</div><div>7z a [圧縮ファイル名].zip [フォルダ名]</div><div>```</div><div><br /></div><div><br /></div><div><br /></div><div><div>問題が起きていたフォルダをこの方法で圧縮してWindows 11マシン上のエクスプローラ・右クリックで解凍して見たところ、文字化けすることなく正しく表示されました!</div><div><br /></div></div><div><br /></div><div><br /></div><div><br /></div><div>以上で解決なのですが、ついでにFinderの右クリックメニューから一発で上のコマンドを実行する方法もメモしておきます。</div><div><br /></div><div><br /></div><h2 style="text-align: left;">Automator で Quick Action を作成する</h2><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEjIPk_STZRJf3Ohis8zLuUB19tKQx3EjMK9IAejAOimr52k3F5U6pKZByE2_5RcEuEl96JnCnYq-6GSoOfQ5_U_orStarsOI5mavXaQB9dTmSsPQol7vQ4gSU2dDcyvgDimZ-alFl5GiYK95AtAqWr7UJlAicdztyEnammpmAWQ58mI94sTHA=s1262" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="872" data-original-width="1262" height="276" src="https://blogger.googleusercontent.com/img/a/AVvXsEjIPk_STZRJf3Ohis8zLuUB19tKQx3EjMK9IAejAOimr52k3F5U6pKZByE2_5RcEuEl96JnCnYq-6GSoOfQ5_U_orStarsOI5mavXaQB9dTmSsPQol7vQ4gSU2dDcyvgDimZ-alFl5GiYK95AtAqWr7UJlAicdztyEnammpmAWQ58mI94sTHA=w400-h276" width="400" /></a></div><br /><div><br /></div><div><br /></div><div><br /></div><div><b>スクリプトの内容</b></div><div>```</div><div><div>ZIPNAME="$1-"$(date +"%Y%m%d-%H%M%S")</div><div>/usr/local/bin/7z a "$ZIPNAME.zip" "$@"</div></div><div>```</div><div><br /></div><div><br /></div><div>「<b>$@</b>」を使うことで複数選択にも対応出来ます。</div><div><br /></div><div><br /></div><div>このQuick Actionを保存すれば、FinderのQuick Actionsメニューから実行出来るようになります。</div><div><br /></div><div><br /></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEgUGMf40vLXIN8DrclH5S5SNf_olZiwqSjElSLXjwFuA23YIwJVwS6jTTzZOvVnEYKowvgQgKcJ_haufQp6NuQYDw5qK6TPpiMEWYrEnd5FHH2b7Aj3OOnyyXZ4f9wre78DOAfk5KRI54MqOM6WPHfD2-_zBjREPFGLbjZRJJvvuYTl9jjPig=s1118" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="980" data-original-width="1118" height="281" src="https://blogger.googleusercontent.com/img/a/AVvXsEgUGMf40vLXIN8DrclH5S5SNf_olZiwqSjElSLXjwFuA23YIwJVwS6jTTzZOvVnEYKowvgQgKcJ_haufQp6NuQYDw5qK6TPpiMEWYrEnd5FHH2b7Aj3OOnyyXZ4f9wre78DOAfk5KRI54MqOM6WPHfD2-_zBjREPFGLbjZRJJvvuYTl9jjPig=s320" width="320" /></a></div><br /><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div> </div>Makotohttp://www.blogger.com/profile/09442687702666423250noreply@blogger.comtag:blogger.com,1999:blog-37629199.post-33808706292600323892021-07-20T16:34:00.054+09:002021-07-20T19:03:47.868+09:00TailwindCSS + Alpine.js でモーダルダイアログを作ろう最近知ったのですが、 <a href="https://tailwindcss.com/" target="_blank">TailwindCSS</a> と <a href="http://Alpine.js" target="_blank">Alpine.js</a> の組み合わせがなかなか使いやすかったので紹介します。<div><br /></div><div><br /><div><br />
<div style="display: grid; justify-items: center;">
<iframe allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts" src="https://codesandbox.io/embed/fancy-bush-22qyf?autoresize=1&fontsize=14&hidenavigation=1&theme=light&view=preview" style="border-radius: 4px; border: 0; height: 800px; overflow: hidden; width: 100%;" title="fancy-bush-22qyf"></iframe>
</div>
<div><br /></div><div><br /></div><div><br /></div><div><br /></div><div>ソースコード</div><div><a href="https://codesandbox.io/s/fancy-bush-22qyf?file=/index.html" target="_blank">https://codesandbox.io/s/fancy-bush-22qyf?file=/index.html</a></div><div><br /></div></div></div><div><br /></div><div><br /></div><div>作った手順は次の通りです。</div><div><br /></div><div><br /></div><h3 style="text-align: left;">1. TailwindCSS をCDNから読み込む。</h3><div><br /></div><div>headタグ内に次のlinkタグを追加します。</div><div><br /></div><div><div style="background-color: #151515; color: #999999; font-family: MonoLisa, Menlo, Monaco, "Courier New", monospace; font-size: 16px; line-height: 24px; white-space: pre;"><div> <span style="color: #abb2bf;"><</span><span style="color: #e06c75;">link</span><span style="color: #abb2bf;"> </span><span style="color: #d19a66;">href</span><span style="color: #abb2bf;">=</span><span style="color: #98c379;">"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css"</span><span style="color: #abb2bf;"> </span><span style="color: #d19a66;">rel</span><span style="color: #abb2bf;">=</span><span style="color: #98c379;">"stylesheet"</span><span style="color: #abb2bf;"> /></span></div><div></div></div></div><div><br /></div><div><br /></div><div>参考: <a href="https://tailwindcss.com/docs/installation#using-tailwind-via-cdn" target="_blank">https://tailwindcss.com/docs/installation#using-tailwind-via-cdn</a></div><div><br /></div><div><br /></div><div><br /></div><h3 style="text-align: left;">2. Alpine.js をCDNから読み込む。</h3><div><br /></div><div>headタグ内に次のstyleタグを追加します。</div><div><br /></div><div><div style="background-color: #151515; color: #999999; font-family: MonoLisa, Menlo, Monaco, "Courier New", monospace; font-size: 16px; line-height: 24px; white-space: pre;"><div> <span style="color: #abb2bf;"><</span><span style="color: #e06c75;">script</span><span style="color: #abb2bf;"> </span><span style="color: #d19a66;">defer</span><span style="color: #abb2bf;"> </span><span style="color: #d19a66;">src</span><span style="color: #abb2bf;">=</span><span style="color: #98c379;">"https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"</span><span style="color: #abb2bf;">></span><span style="color: #abb2bf;"><</span><span style="color: #abb2bf;">/</span><span style="color: #e06c75;">script</span><span style="color: #abb2bf;">></span></div><div></div></div></div><div><br /></div><div>参考: <a href="https://alpinejs.dev/essentials/installation" target="_blank">https://alpinejs.dev/essentials/installation</a></div><div><br /></div><div><br /></div><h3 style="text-align: left;">3. ページのレイアウトを作る。</h3><div><br /></div><div>bodyタグ直下のdivタグの中に、header, main, footer の3つの要素を作成します。</div><div><br /></div><div>親のdivのスタイルで display: grid とし、それぞれのエリアの高さが header 6rem, main 1fr, footer 4rem になるように指定します。また w-screen, h-screen で親のdivが画面一杯に広がるようにしています。</div><div><br /></div><div><div style="background-color: #151515; font-family: MonoLisa, Menlo, Monaco, "Courier New", monospace; font-size: 16px; line-height: 24px; white-space: pre;"><div style="color: #999999;"> <span style="color: #abb2bf;"><</span><span style="color: #e06c75;">div</span></div><div><span style="color: #abb2bf;"> </span><span style="color: #abb2bf;"> </span><span style="color: #d19a66;">class</span><span style="color: #abb2bf;">=</span><span style="color: #98c379;">"m-0 p-0 </span><b><span style="color: #ff00fe;"><u>w-screen h-screen grid</u></span></b><span style="color: #98c379;">"</span></div><div><span style="color: #abb2bf;"> </span><span style="color: #d19a66;">style</span><span style="color: #abb2bf;">=</span><span style="color: #98c379;">"</span><span style="color: #98c379;">grid-template-rows: </span><b><u><span style="color: #ff00fe;">6rem 1fr 4rem</span></u></b><span style="color: #98c379;">;"</span></div><div style="color: #999999;"><span style="color: #abb2bf;"> ><br /></span></div><div style="color: #999999;"></div></div></div><div><br /></div><div><br /></div><div>詳細はソースコードを参照してください。</div><div><a href="https://codesandbox.io/s/fancy-bush-22qyf?file=/index.html" target="_blank">https://codesandbox.io/s/fancy-bush-22qyf?file=/index.html</a></div><div><br /></div><div><br /></div><div>下の画像のような表示になればOKです。</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-rciG50EjtLQ/YPaKHlUM31I/AAAAAAAAerc/5KfERSrOrtgwb88TP285Lp_c2z6I2PsEgCLcBGAsYHQ/s1333/22qyf.csb.app_%2528iPhone%2B6_7_8%2529.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1333" data-original-width="750" height="320" src="https://1.bp.blogspot.com/-rciG50EjtLQ/YPaKHlUM31I/AAAAAAAAerc/5KfERSrOrtgwb88TP285Lp_c2z6I2PsEgCLcBGAsYHQ/s320/22qyf.csb.app_%2528iPhone%2B6_7_8%2529.png" /></a></div><br /><div><br /></div><div><div>mainに overflow-y-auto クラスを指定しているので、コンテンツが画面に収まらない場合はフッターやヘッダーを固定にしたままmain部分だけを縦スクロールさせることが出来ます。</div><div><br /></div></div><div><div style="background-color: #151515; font-family: MonoLisa, Menlo, Monaco, "Courier New", monospace; font-size: 16px; line-height: 24px; white-space: pre;"><span style="color: #abb2bf;"><</span><span style="color: #e06c75;">main</span><span style="color: #abb2bf;"> </span><span style="color: #d19a66;">class</span><span style="color: #abb2bf;">=</span><span style="color: #98c379;">"p-6 </span><b><u><span style="color: #ff00fe;">overflow-y-auto</span></u></b><span style="color: #98c379;">"</span><span style="color: #abb2bf;">></span></div></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div>画面の中央にある「開く」ボタンのクラス指定は次のようになっています。(一部省略)</div><div><br /></div><div><div style="background-color: #151515; color: #999999; font-family: MonoLisa, Menlo, Monaco, "Courier New", monospace; font-size: 16px; line-height: 24px; white-space: pre;"><div><span style="color: #abb2bf;"><</span><span style="color: #e06c75;">button</span></div><div><span style="color: #abb2bf;"> </span><span style="color: #d19a66;">class</span><span style="color: #abb2bf;">=</span><span style="color: #98c379;">"py-2 px-4 max-h-12 bg-blue-600 </span></div><div><span style="color: #98c379;"> hover:bg-blue-400 text-white border rounded-md shadow"</span></div><div><span style="color: #abb2bf;">></span></div><div></div></div></div><div><br /></div><div><br /></div><div>TailwindCSS のおかげでそれなりに見栄えの良いボタンになります。</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-qfHd--GBSXg/YPaWoTTmTAI/AAAAAAAAerw/gmBUjr3yPmE03SaIjeg3fmYFqAbngJOnwCLcBGAsYHQ/s568/Screen%2BShot%2B2021-07-19%2Bat%2B11.25.28%2BPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="302" data-original-width="568" src="https://1.bp.blogspot.com/-qfHd--GBSXg/YPaWoTTmTAI/AAAAAAAAerw/gmBUjr3yPmE03SaIjeg3fmYFqAbngJOnwCLcBGAsYHQ/s320/Screen%2BShot%2B2021-07-19%2Bat%2B11.25.28%2BPM.png" width="320" /></a></div><br /><div><br /></div><div><br /></div><div><br /></div><h3 style="text-align: left;">4. モーダルダイアログのHTMLを追加。</h3><div><br /></div><div>親divの一番最後に下のHTMLを追加します。</div><div><br /></div><div><div style="background-color: #151515; color: #999999; font-family: MonoLisa, Menlo, Monaco, "Courier New", monospace; font-size: 16px; line-height: 24px; white-space: pre;"><div><span style="color: #abb2bf;"><!-- モーダルダイアログのラッパー --></span></div><div><span style="color: #abb2bf;"><</span><span style="color: #e06c75;">div</span><span style="color: #abb2bf;">></span></div><div><span style="color: #abb2bf;"> <!-- 背景を暗くするための半透明部分 --></span></div><div> <span style="color: #abb2bf;"><</span><span style="color: #e06c75;">div</span><span style="color: #abb2bf;">></</span><span style="color: #e06c75;">div</span><span style="color: #abb2bf;">></span></div><div><span style="color: #abb2bf;"><br /></span></div><div><span style="color: #abb2bf;"> <!-- 実際のモーダルダイアログ部分 --></span></div><div> <span style="color: #abb2bf;"><</span><span style="color: #e06c75;">div</span><span style="color: #abb2bf;">></span></div><div> <span style="color: #abb2bf;"><</span><span style="color: #e06c75;">header</span><span style="color: #abb2bf;">></span></div><div> <span style="color: #abb2bf;"><</span><span style="color: #e06c75;">h1</span><span style="color: #abb2bf;">></span>確認してください<span style="color: #abb2bf;"></</span><span style="color: #e06c75;">h1</span><span style="color: #abb2bf;">></span></div><div> <span style="color: #abb2bf;"></</span><span style="color: #e06c75;">header</span><span style="color: #abb2bf;">></span></div><div><span style="color: #abb2bf;"><br /></span></div><div> <m<span style="color: #e06c75;">ain</span><span style="color: #abb2bf;">></span></div><div> <span style="color: #abb2bf;"><</span><span style="color: #e06c75;">p</span><span style="color: #abb2bf;">></span>本当にこの操作を実行しますか?<span style="color: #abb2bf;"></</span><span style="color: #e06c75;">p</span><span style="color: #abb2bf;">></span></div><div> <span style="color: #abb2bf;"></</span><span style="color: #e06c75;">main</span><span style="color: #abb2bf;">></span></div><div><span style="color: #abb2bf;"><br /></span></div><div> <span style="color: #abb2bf;"><</span><span style="color: #e06c75;">footer</span><span style="color: #abb2bf;">></span></div><div> <span style="color: #abb2bf;"><</span><span style="color: #e06c75;">button</span><span style="color: #abb2bf;">></span>キャンセル<span style="color: #abb2bf;"></</span><span style="color: #e06c75;">button</span><span style="color: #abb2bf;">></span></div><div> <span style="color: #abb2bf;"><</span><span style="color: #e06c75;">button</span><span style="color: #abb2bf;">></span>実行!<span style="color: #abb2bf;"></</span><span style="color: #e06c75;">button</span><span style="color: #abb2bf;">></span></div><div> <span style="color: #abb2bf;"></</span><span style="color: #e06c75;">footer</span><span style="color: #abb2bf;">></span></div><div> <span style="color: #abb2bf;"></</span><span style="color: #e06c75;">div</span><span style="color: #abb2bf;">></span></div><div><span style="color: #abb2bf;"></</span><span style="color: #e06c75;">div</span><span style="color: #abb2bf;">></span></div><div></div></div></div><div><br /></div><div><br /></div><h3 style="text-align: left;">5. モーダルダイアログのスタイルを設定。</h3><div><br /></div><div>ラッパー用divに次のクラスを指定して、画面一杯をカバーするようにします。</div><div><br /></div><div><div style="background-color: #151515; color: #999999; font-family: MonoLisa, Menlo, Monaco, "Courier New", monospace; font-size: 16px; line-height: 24px; white-space: pre;"><div><span style="color: #abb2bf;"><</span><span style="color: #e06c75;">div</span><span style="color: #abb2bf;"> </span><span style="color: #d19a66;">class</span><span style="color: #abb2bf;">=</span><span style="color: #98c379;">"absolute top-0 left-0 w-screen h-screen"</span><span style="color: #abb2bf;">></span></div><div></div></div></div><div><br /></div><div><br /></div><div>次に背景を暗くするためのdivのスタイルを設定します。</div><div><br /></div><div><div style="background-color: #151515; color: #999999; font-family: MonoLisa, Menlo, Monaco, "Courier New", monospace; font-size: 16px; line-height: 24px; white-space: pre;"> <span style="color: #abb2bf;"><</span><span style="color: #e06c75;">div</span><span style="color: #abb2bf;"> </span><span style="color: #d19a66;">class</span><span style="color: #abb2bf;">=</span><span style="color: #98c379;">"absolute w-full h-full bg-black opacity-80"</span><span style="color: #abb2bf;">></</span><span style="color: #e06c75;">div</span><span style="color: #abb2bf;">></span></div></div><div><br /></div><div><br /></div><div>ダイアログ本体部分のスタイル指定は下のようになります。</div><div><br /></div><div><div style="background-color: #151515; color: #999999; font-family: MonoLisa, Menlo, Monaco, "Courier New", monospace; font-size: 16px; line-height: 24px; white-space: pre;"><div> <span style="color: #abb2bf;"><</span><span style="color: #e06c75;">div</span></div><div><span style="color: #abb2bf;"> </span><span style="color: #d19a66;">class</span><span style="color: #abb2bf;">=</span><span style="color: #98c379;">"relative w-5/6 max-w-xl h-1/2 m-auto grid bg-gray-300 border rounded-md shadow"</span></div><div><span style="color: #abb2bf;"> </span><span style="color: #d19a66;">style</span><span style="color: #abb2bf;">=</span><span style="color: #98c379;">"</span><span style="color: #98c379;">top: 20vh; grid-template-rows: 4rem 1fr 6rem;"</span></div><div><span style="color: #abb2bf;"> ></span></div><div></div></div></div><div><br /></div><div><br /></div><div>TailwindCSSで定義されていない値についてはstyle属性で独自に指定しています。</div><div><br /></div><div>最終的にはこのようなダイアログになります。</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-SVWBbJr6j2I/YPaQ_b_qpII/AAAAAAAAerk/iC6f3hTT2rQeORGbv6CBZeBHw3nG33VHwCLcBGAsYHQ/s1333/22qyf.csb.app_%2528iPhone%2B6_7_8%2529%2B%25281%2529.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1333" data-original-width="750" height="320" src="https://1.bp.blogspot.com/-SVWBbJr6j2I/YPaQ_b_qpII/AAAAAAAAerk/iC6f3hTT2rQeORGbv6CBZeBHw3nG33VHwCLcBGAsYHQ/s320/22qyf.csb.app_%2528iPhone%2B6_7_8%2529%2B%25281%2529.png" /></a></div><br /><div><br /></div><div><br /></div><div><br /></div><div><br /></div><h3 style="text-align: left;">6. 初期状態でモーダルダイアログを非表示にする。</h3><div><br /></div><div>ここから Alpine.js が活躍します。</div><div><br /></div><div>まず、ページの一番親(body直下)のdivに <b>x-data</b> 属性を追加します。</div><div><br /></div><div><div style="background-color: #151515; font-family: MonoLisa, Menlo, Monaco, "Courier New", monospace; font-size: 16px; line-height: 24px; white-space: pre;"><div style="color: #999999;"><span style="color: #abb2bf;"><</span><span style="color: #e06c75;">div</span></div><div><span style="color: #abb2bf;"> </span><span style="color: #ff00fe;"><u><b>x-data="{ open : false }"</b></u></span></div><div style="color: #999999;"><span style="color: #abb2bf;"> </span><span style="color: #d19a66;">class</span><span style="color: #abb2bf;">=</span><span style="color: #98c379;">"m-0 p-0 w-screen h-screen grid"</span></div><div style="color: #999999;"><span style="color: #abb2bf;"> </span><span style="color: #d19a66;">style</span><span style="color: #abb2bf;">=</span><span style="color: #98c379;">"</span><span style="color: #98c379;">grid-template-rows: 6rem 1fr 4rem;"</span></div><div style="color: #999999;"><span style="color: #abb2bf;">></span></div><div style="color: #999999;"></div></div></div><div><br /></div><div>次にモーダルダイアログのラッパーのdivに <b>x-show</b> 属性を追加します。</div><div><br /></div><div><div style="background-color: #151515; font-family: MonoLisa, Menlo, Monaco, "Courier New", monospace; font-size: 16px; line-height: 24px; white-space: pre;"><div style="color: #999999;"><span style="color: #5c6370; font-style: italic;"><!-- モーダルダイアログ --></span></div><div><span style="color: #abb2bf;"><</span><span style="color: #e06c75;">div</span><span style="color: #abb2bf;"> </span><b><u><span style="color: #ff00fe;">x-show="open"</span></u></b><span style="color: #abb2bf;"> </span><span style="color: #d19a66;">class</span><span style="color: #abb2bf;">=</span><span style="color: #98c379;">"absolute top-0 left-0 w-screen h-screen"</span><span style="color: #abb2bf;">></span></div><div style="color: #999999;"></div></div></div><div><br /></div><div><br /></div><div>これでモーダルダイアログが初期状態では表示されなくなります。</div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><h3 style="text-align: left;">7. 「開く」ボタンでモーダルダイアログを表示する。</h3><div><br /></div><div>ページ中央の「開く」ボタンに <b>@click="open = true"</b> を追加します。</div><div><br /></div><div><div style="background-color: #151515; font-family: MonoLisa, Menlo, Monaco, "Courier New", monospace; font-size: 16px; line-height: 24px; white-space: pre;"><div style="color: #999999;"> <span style="color: #abb2bf;"><</span><span style="color: #e06c75;">button</span></div><div><span style="color: #abb2bf;"> </span><b><u><span style="color: #ff00fe;">@click="open = true"</span></u></b></div><div style="color: #999999;"><span style="color: #abb2bf;"> </span><span style="color: #d19a66;">class</span><span style="color: #abb2bf;">=</span><span style="color: #98c379;">"py-2 px-4 max-h-12 bg-blue-600 ...</span></div><div style="color: #999999;"><span style="color: #abb2bf;"> ></span>開く<span style="color: #abb2bf;"></</span><span style="color: #e06c75;">button</span><span style="color: #abb2bf;">></span></div><div style="color: #999999;"></div></div></div><div><br /></div><div><br /></div><div>これで「開く」ボタンをクリックするとモーダルダイアログが表示されるようになりました!</div><div><br /></div><div><br /></div><h3 style="text-align: left;">8. 「キャンセル」ボタンでモーダルダイアログを閉じる。</h3><div><br /></div><div>モーダルダイアログ内の「キャンセル」ボタンでは「開く」ボタンと反対に "open = false" とすることでダイアログを非表示にします。</div><div><br /></div><div><div style="background-color: #151515; font-family: MonoLisa, Menlo, Monaco, "Courier New", monospace; font-size: 16px; line-height: 24px; white-space: pre;"><div style="color: #999999;"><span style="color: #abb2bf;"> <</span><span style="color: #e06c75;">button</span></div><div><span style="color: #abb2bf;"> </span><b><u><span style="color: #ff00fe;">@click="open = false"</span></u></b></div><div style="color: #999999;"><span style="color: #abb2bf;"> </span><span style="color: #d19a66;">class</span><span style="color: #abb2bf;">=</span><span style="color: #98c379;">"py-2 px-4 text-white bg-gray-600 ..."</span></div><div style="color: #999999;"><span style="color: #abb2bf;"> ></span>キャンセル<span style="color: #abb2bf;"></</span><span style="color: #e06c75;">button</span><span style="color: #abb2bf;">></span></div></div></div><div><br /></div><div><br /></div><div><b><span style="font-size: large;">Alpine.js シンプルで良いですね!</span></b></div><div><br /></div><div><br /></div><div>ちなみに<b>x-show</b>での表示・非表示切替時にトランジションアニメーションを付けるには、<b>x-transition</b> 属性を付加するだけで大丈夫です。</div><div><br /></div><div><br /></div><div><b>x-transition</b> ではデフォルトで250msの scale + opacityのアニメーションが実行されます。これをopacityのみに変更したい場合は、<b>x-transition</b> を <b>x-transition.opacity</b> とすればOKです。もちろん実行時間の指定も可能です。</div><div><br /></div><div>参考: <a href="https://alpinejs.dev/directives/transition#customizing-opacity" target="_blank">https://alpinejs.dev/directives/transition#customizing-opacity</a></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><h3 style="text-align: left;">9. 外側をクリックされたらモーダルダイアログを閉じる。</h3><div><br /></div><div><b>@click.outside</b> という表記を使えば、その要素の外側がクリックされた時の処理を書くことが出来ます。</div><div><br /></div><div><div style="background-color: #151515; font-family: MonoLisa, Menlo, Monaco, "Courier New", monospace; font-size: 16px; line-height: 24px; white-space: pre;"><div style="color: #999999;"><span style="color: #5c6370; font-style: italic;"><!-- モーダルダイアログ --></span></div><div style="color: #999999;"><span style="color: #abb2bf;"><</span><span style="color: #e06c75;">div</span><span style="color: #abb2bf;"> </span><span style="color: #d19a66;">x-show</span><span style="color: #abb2bf;">=</span><span style="color: #98c379;">"open"</span><span style="color: #abb2bf;"> </span><span style="color: #d19a66;">class</span><span style="color: #abb2bf;">=</span><span style="color: #98c379;">"absolute top-0 left-0 ..."</span><span style="color: #abb2bf;">></span></div><div style="color: #999999;"> <span style="color: #abb2bf;"><</span><span style="color: #e06c75;">div</span><span style="color: #abb2bf;"> </span><span style="color: #d19a66;">class</span><span style="color: #abb2bf;">=</span><span style="color: #98c379;">"absolute w-full h-full bg-black ..."</span><span style="color: #abb2bf;">></</span><span style="color: #e06c75;">div</span><span style="color: #abb2bf;">></span></div><div style="color: #999999;"> <span style="color: #abb2bf;"><</span><span style="color: #e06c75;">div</span></div><div><span style="color: #abb2bf;"> </span><b><u><span style="color: #ff00fe;">@click.outside="open = false"</span></u></b></div><div style="color: #999999;"><span style="color: #abb2bf;"> </span><span style="color: #d19a66;">class</span><span style="color: #abb2bf;">=</span><span style="color: #98c379;">"relative w-5/6 max-w-xl h-1/2 m-auto ..."</span></div><div style="color: #999999;"><span style="color: #abb2bf;"> </span><span style="color: #d19a66;">style</span><span style="color: #abb2bf;">=</span><span style="color: #98c379;">"</span><span style="color: #98c379;">top: 20vh; grid-template-rows: 4rem 1fr 6rem;"</span></div><div style="color: #999999;"><span style="color: #abb2bf;"> ></span></div><div style="color: #999999;"></div></div></div><div><br /></div><div><br /></div><div><br /></div><h3 style="text-align: left;">10. 「実行!」ボタンが押されたらモーダルダイアログを閉じてカスタムイベントを発行する。</h3><div><br /></div><div>「実行!」ボタンの処理もキャンセルと同じように <b>@click="..."</b>の中に全て書いてしまっても良いのですが、ここでは Alpine.js のカスタムイベント発行の機能を使ってみます。 </div><div><br /></div><div><div style="background-color: #151515; font-family: MonoLisa, Menlo, Monaco, "Courier New", monospace; font-size: 16px; line-height: 24px; white-space: pre;"><div style="color: #999999;"> <span style="color: #abb2bf;"><</span><span style="color: #e06c75;">button</span></div><div><span style="color: #abb2bf;"> </span><span style="color: #d19a66;">@click</span><span style="color: #abb2bf;">=</span><span style="color: #98c379;">"open = false; </span><b><u><span style="color: #ff00fe;">$dispatch('dialog-ok')</span></u></b><span style="color: #98c379;">"</span></div><div style="color: #999999;"><span style="color: #abb2bf;"> </span><span style="color: #d19a66;">class</span><span style="color: #abb2bf;">=</span><span style="color: #98c379;">"py-2 px-4 text-white bg-red-600 ...</span><span style="color: #98c379;">"</span></div><div style="color: #999999;"><span style="color: #abb2bf;"> ></span>実行!<span style="color: #abb2bf;"></</span><span style="color: #e06c75;">button</span><span style="color: #abb2bf;">></span></div><div style="color: #999999;"></div></div></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><h3 style="text-align: left;">11. カスタムイベントを受け取ったら何らかの処理を実行する。</h3><div><br /></div><div><b>$dispatch()</b> で発行したカスタムイベントはDOMツリーの上位にある全ての要素で受け取ることが可能です。</div><div><br /></div><div>ここでは body直下のdivにイベント処理を追加しました。</div><div><br /></div><div><div style="background-color: #151515; font-family: MonoLisa, Menlo, Monaco, "Courier New", monospace; font-size: 16px; line-height: 24px; white-space: pre;"><div style="color: #999999;"><span style="color: #abb2bf;"><</span><span style="color: #e06c75;">div</span></div><div style="color: #999999;"><span style="color: #abb2bf;"> </span><span style="color: #d19a66;">x-data</span><span style="color: #abb2bf;">=</span><span style="color: #98c379;">"{ open : false }"</span></div><div><span style="color: #abb2bf;"> </span><b><u><span style="color: #ff00fe;">@dialog-ok=</span><span style="color: #98c379;">"setTimeout(() => alert('Hi'), 100)"</span></u></b></div><div style="color: #999999;"><span style="color: #abb2bf;"> </span><span style="color: #d19a66;">class</span><span style="color: #abb2bf;">=</span><span style="color: #98c379;">"m-0 p-0 w-screen h-screen grid"</span></div><div style="color: #999999;"><span style="color: #abb2bf;">></span></div><div style="color: #999999;"></div></div></div><div><br /></div><div><br /></div><div>setTimeout()を使っているのは、これが無いとモーダルダイアログが閉じる前にアラートが表示されてしまっていたためです。</div><div><br /></div><div><br /></div><div><br /></div><h3 style="text-align: left;">まとめ</h3><div><br /></div><div>TailwindCSSもAlpine.jsも、覚えないといけないことが少なくてすんなりと習得出来そうです。</div><div><br /></div><div>TailwindCSSに慣れるとスタイルの設定にかかる時間が格段に短縮されます。Alpine.jsも必要最小限の使い方さえ覚えれば簡単な動作であればさくさくと実装出来て、とても便利です。</div><div><br /></div><div><br /></div><div>興味を持った方はぜひ試してみてください!</div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div> 🍻</div>Makotohttp://www.blogger.com/profile/09442687702666423250noreply@blogger.comtag:blogger.com,1999:blog-37629199.post-19263361312664621442020-12-25T11:33:00.012+09:002020-12-25T13:15:58.568+09:00Androidで単語帳を作ろう(3)- SQLiteデータベースの使い方<h2 style="text-align: left;">6. SQLiteデータベースの使い方</h2><div><br /></div><div><br /></div><div>公式の開発者向けサイトでは、Room というライブラリを使ってデータベースアクセスを行うことが<a href="https://developer.android.com/training/data-storage/room?hl=ja" rel="nofollow" target="_blank">推奨</a>されています。</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://developer.android.com/training/data-storage/room?hl=ja" rel="nofollow" style="margin-left: 1em; margin-right: 1em;" target="_blank"><img border="0" data-original-height="882" data-original-width="2262" height="250" src="https://1.bp.blogspot.com/-5KSEITyfamM/X-U8Pjt6G3I/AAAAAAAAdJI/8qRcnv4HmH0bQ0ehxH9cEc04KqSkhRANACLcBGAsYHQ/w640-h250/Screen%2BShot%2B2020-12-24%2Bat%2B3.11.03%2BPM.png" width="640" /></a></div><br /><div><br /></div><div><br /></div><div>もちろんこれにしたがって Room を使っても良いのですが、今回はまずはシンプルに自分でSQLクエリーを発行してデータベース処理を実装していくことにしました。</div><div><br /></div><div><br /></div><div><br /></div><h3 style="text-align: left;">SQLiteOpenHelperを使う</h3><div><br /></div><div>SQLiteデータベースの初期化と接続の管理を行うには、<a href="https://developer.android.com/training/data-storage/sqlite?hl=ja#DbHelper" rel="nofollow" target="_blank">SQLiteOpenHelper</a>を継承したクラスを作成するのが便利です。</div><div><br /></div><div><br /></div>
<div>--- DatabaseHelper.java</div>
<script src="https://gist.github.com/makotoishida/d44568f08d349f81101b659336785bd1.js"></script>
<div>---</div>
<div><br /></div><div><br /></div><div>テーブル名、カラム名などの固定文字列は、後述するWordsRepositoryクラス内に定数として定義してあるので、二重で定義しないようにそちらを参照しています。</div><div><br /></div><div><br /></div><div>今後データベースにテーブルを追加したりカラムを追加したりする場合は、データベースのバージョン番号を上げて、onUpgrade() メソッド内で必要なSQLを発行する処理を実行します。今は最初のバージョンなのでまだ何も実装していません。</div><div><br /></div><div><br /></div><div><br /></div><h3 style="text-align: left;">アプリケーション全体を通してデータベース接続を保持する</h3><div><br /></div><div>では、上で作成したDatabaseHelperクラスをどのように使うかを見てみましょう。</div><div><br /></div><div><div></div></div><blockquote><div><div><span style="font-family: Source Sans Pro;"><span style="color: #444444;"> DatabaseHelper dbHelper = </span><span style="color: #2b00fe;"><b>new DatabaseHelper(this)</b></span><span style="color: #444444;">;</span></span></div><div><span style="font-family: Source Sans Pro;"><span style="color: #444444;"><span> </span>SQLiteDatabase db = dbHelper.</span><span style="color: #2b00fe;"><b>getWritableDatabase()</b></span><span style="color: #444444;">;</span></span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> try(final Cursor cursor = db.rawQuery("SELECT * FROM words", null)){</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> while (cursor.moveToNext()){</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> (...)</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> }</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> }</span></div></div><div></div></blockquote><div><br /></div><div>tryのカッコ内で Cursor をオープンすると、tryを抜けたときに自動でクローズしてくれるので便利ですね。</div><div><br /></div><div><br /></div><div>このようにデータベースアクセスが必要になる度に毎回 DatabaseHelperのインスタンスを生成してから getWritableDatabase()メソッドを呼んでも構わないのですが、この方法だとデータベースのオープン/クローズ処理が毎回行われることになります。</div><div><br /></div><div><br /></div><div>今回のサンプルアプリケーションでは毎回データベース接続をオープン/クローズするのではなく、「カスタムアプリケーションクラス」を使ってアプリケーション全体を通してデータベース接続を保持する方法を使うことにします。</div><div><br /></div><div><br /></div><div> 🎬[Android]Applicationクラスとは</div><div><span> </span><a href="https://www.youtube.com/watch?v=A3PVM6xs5cY" rel="nofollow" target="_blank">https://www.youtube.com/watch?v=A3PVM6xs5cY</a></div><div><br /></div><div><br /></div><div><br /></div>
<div>--- MyApplication.java</div>
<script src="https://gist.github.com/makotoishida/899cf885b2e6c07ec02e45c556299222.js"></script>
<div>---</div>
<div><br /></div><div><br /></div><div>Applicationクラスのインスタンスはアプリケーションのプロセスが動いているかぎり破棄されることはないので、このクラス内のインスタンス変数として変数を宣言しておけばアプリケーションのどこからでも共通に使うことができます。</div><div><br /></div><div><br /></div><div>今回はこれを利用してDatabaseHelperのインスタンスをApplicationクラスで保持することにしました。getDb()というメソッドが初めて呼ばれたときにデータベースをオープンします。</div><div><br /></div><div>また、アプリケーションが終了するときに onTerminate() が呼ばれるので、このタイミングでデータベース接続をクローズしています。</div><div><br /></div><div><br /></div><div><br /></div><div>カスタムアプリケーションクラスが起動時に正しく呼ばれるようにするには、マニフェストファイルでクラス名を指定しておく必要があります。</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-YkXiM233yiU/X-VIUBqZidI/AAAAAAAAdJU/h5SDswWpsskM6YFazRt1bJ6T-sw_F1KuACLcBGAsYHQ/s1472/Screen%2BShot%2B2020-12-24%2Bat%2B4.01.47%2BPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="786" data-original-width="1472" height="342" src="https://1.bp.blogspot.com/-YkXiM233yiU/X-VIUBqZidI/AAAAAAAAdJU/h5SDswWpsskM6YFazRt1bJ6T-sw_F1KuACLcBGAsYHQ/w640-h342/Screen%2BShot%2B2020-12-24%2Bat%2B4.01.47%2BPM.png" width="640" /></a></div><div style="text-align: center;"></div><br /><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><h3 style="text-align: left;">リポジトリクラスを使ってデータベース処理を一箇所にまとめる</h3><div><br /></div><div>次に、実際にデータベースに対してSQLクエリーを発行してデータを取得したり更新したりする処理を実装します。これらの処理はテーブル単位で実行されることが多いので、その単位で「リポジトリクラス」を作ってまとめるのが良いでしょう。</div><div><br /></div><div><br /></div><div>今回のサンプルアプリケーションでは、WordsRepository というクラスを作って単語データに関するデータベース処理を記述しました。</div><div><br /></div><div><br /></div><h3 style="text-align: left;">全件を取得する</h3><div><br /></div><div><div><span style="color: #444444; font-family: Source Sans Pro; font-size: x-small;"></span></div></div><blockquote><div><div><span style="color: #444444; font-family: Source Sans Pro;"> public List<Word> getList() {</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> ArrayList<Word> list = new ArrayList<>();</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"><br /></span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> // tryの括弧内でCursorを生成することで自動的にcloseされる。</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> try(final Cursor cursor = mDb.rawQuery("SELECT * FROM " + TABLE_NAME + " ORDER BY " + COL_ID, null)){</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> while (cursor.moveToNext()){</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> final Word word = buildWordFromCursor(cursor);</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> list.add(word);</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> }</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> }</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"><br /></span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> return list;</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> }</span></div></div><div></div></blockquote><div><span style="font-family: Source Sans Pro; font-size: x-small;"><br /></span></div><div><br /></div><h3 style="text-align: left;">1件だけを取得する</h3><div><br /></div><div><div><span style="color: #444444; font-family: Source Sans Pro; font-size: x-small;"></span></div></div><blockquote><div><div><span style="color: #444444; font-family: Source Sans Pro;"> // idで指定された単語を返す。idが0の場合は新規インスタンスを生成して返す。idが見つからない場合はnullを返す。</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> public Word getById(int id) {</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> if (id == 0){</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> return new Word(0, "", "");</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> }</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"><br /></span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> Word word = null;</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> String[] args = { Integer.toString(id) };</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"><br /></span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> // tryの括弧内でCursorを生成することで自動的にcloseされる。</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> try (final Cursor cursor = mDb.rawQuery("SELECT * FROM " + TABLE_NAME + " WHERE " + COL_ID + " = ?", args)) {</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> // 主キーで絞っているため結果は1行か0行かのどちらかなのでwhileでループする必要はない。</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> if (cursor.moveToFirst()){</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> word = buildWordFromCursor(cursor);</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> }</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> }</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"><br /></span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> return word;</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> }</span></div></div><div></div></blockquote><div><br /></div><div><br /></div><h3 style="text-align: left;">単語を追加または更新する</h3><div><br /></div><div><div><span style="color: #444444; font-family: Source Sans Pro; font-size: x-small;"></span></div></div><blockquote><div><div><span style="color: #444444; font-family: Source Sans Pro;"> // idが0の場合は新規追加、0以外の場合は更新処理を行う。</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> public void save(Word word) throws InvalidKeyException, SQLException {</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> if (word._id == 0) {</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> String sql = "INSERT INTO " + TABLE_NAME + " ("</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> + COL_ENGLISH + ", "</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> + COL_JAPANESE + ", "</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> + COL_DONE</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> + ") VALUES (?, ?, ?) ";</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> String[] args = { word.english, word.japanese, boolToString(word.done) };</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> mDb.execSQL(sql, args);</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> return;</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> }</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"><br /></span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> Word existing = getById(word._id);</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> if (existing == null) {</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> throw new InvalidKeyException("");</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> }</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"><br /></span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> String sql = "UPDATE " + TABLE_NAME + " SET "</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> + COL_ENGLISH + " = ?, "</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> + COL_JAPANESE + " = ?, "</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> + COL_DONE + " =? "</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> + " WHERE (" + COL_ID + " = ?) ";</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> String[] args = { word.english, word.japanese, boolToString(word.done), Integer.toString(word._id) };</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> mDb.execSQL(sql, args);</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> }</span></div></div><div></div></blockquote><div><br /></div><div><br /></div><div><br /></div><h3 style="text-align: left;">単語を削除する</h3><div><br /></div><div><div><span style="color: #444444; font-family: Source Sans Pro; font-size: x-small;"></span></div></div><blockquote><div><div><span style="color: #444444; font-family: Source Sans Pro;"> // idで指定された単語を削除する。単語が見つからない場合は何もしない。</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> public void delete(int id) throws SQLException {</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> Word existing = getById(id);</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> if (existing == null) return;</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"><br /></span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> String sql = "DELETE FROM " + TABLE_NAME + " WHERE (" + COL_ID + " = ?) ";</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> String [] args = { Integer.toString(id)};</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> mDb.execSQL(sql, args);</span></div><div><span style="color: #444444; font-family: Source Sans Pro;"> }</span></div></div><div></div></blockquote><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div>今回はSQLiteデータベースの使い方について見てみました。</div><div>ひとまずこれで単語帳アプリのVersion 0.1が動くようになりました。</div><div><br /></div><div><br /></div><div>ここまでのソースコードは下のURLで公開していますので良ければプロジェクト全体をクローンして動かしてみてください。</div><div><a href="https://github.com/makotoishida/flashcards-android/commits/blog-v0.1" rel="nofollow" target="_blank">https://github.com/makotoishida/flashcards-android/commits/blog-v0.1</a></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><b>Androidで単語帳を作ろう - 目次</b></div><div><a href="https://blog.makotoishida.com/2020/12/android1.html" target="_blank">Androidで単語帳を作ろう(1)- 画面遷移、アクティビティのライフサイクルなど</a> </div><div><a href="https://blog.makotoishida.com/2020/12/android2.html" target="_blank">Androidで単語帳を作ろう(2)- ListViewの使い方</a> </div><div>Androidで単語帳を作ろう(3)- SQLiteデータベースの使い方</div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div>🍻</div>Makotohttp://www.blogger.com/profile/09442687702666423250noreply@blogger.comtag:blogger.com,1999:blog-37629199.post-67151410462312345392020-12-17T18:40:00.006+09:002020-12-25T12:46:20.009+09:00Androidで単語帳を作ろう(2)- ListViewの使い方 <div>今回は単語一覧画面の実装方法を詳しく見てみましょう。</div><div><br /></div><div><br /></div><h2 style="text-align: left;">5. ListViewの使い方</h2><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitZvc6Q_Nk2xXtgx97ZBIhYRNQFFzhPraWvAQUbzd9SBOdSmbLz-ckxIskEqAXlQIfayTh4rOcTSlOwnM87cq1P72GXOyAN5xbmI-5-nSzxVqdHw2kkqM3pRP-Sbw4zgkv4nl8/s2220/words0.1-list2.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="2220" data-original-width="1080" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEitZvc6Q_Nk2xXtgx97ZBIhYRNQFFzhPraWvAQUbzd9SBOdSmbLz-ckxIskEqAXlQIfayTh4rOcTSlOwnM87cq1P72GXOyAN5xbmI-5-nSzxVqdHw2kkqM3pRP-Sbw4zgkv4nl8/w195-h400/words0.1-list2.png" width="195" /></a></div><br /><div><br /></div><div><br /></div><div><div>このリストでは、1行に「英語」と「覚えたフラグ(チェックマーク)」の2つの項目を表示したいので、カスタムアダプタを実装しました。</div><div><br /></div></div><div><br /></div><div><span> </span>Androidでリストビュー(ListView)をカスタムして表示する - Qiita </div><div><span> </span><a href="https://qiita.com/ksugawara61/items/2d63f0be279a94b74550" rel="nofollow" target="_blank">https://qiita.com/ksugawara61/items/2d63f0be279a94b74550</a></div><div><br /></div><div><br /></div><div><br /></div><div>--- WordListActivity.java </div>
<script src="https://gist.github.com/makotoishida/def367cd811c3f4636bd017ed03f9afe.js"></script>
<br /><br /><br />
<div>--- WordListViewAdapter.java</div>
<script src="https://gist.github.com/makotoishida/85d1d27cc3401f38b4b94199069985dd.js"></script>
<div>---</div>
<div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div>ListViewとカスタムアダプターの実装方法はほぼ決まりきったイディオムのようなものなので、ひとまずこれはこういうものとして覚えておけば良いのではないかと思います。</div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div>ちなみに最近のAndroidではListViewよりも<b>RecyclerView</b>を使う方が推奨されているようです。ListViewと実装方法においてそれほど大きな違いはなさそうですが、こちらも一応確認しておきたいところです。</div><div><br /></div><div> 🎬[Android]RecyclerViewの仕組み</div><div><span> </span><a href="https://www.youtube.com/watch?v=grshfh_bpwo" rel="nofollow" target="_blank">https://www.youtube.com/watch?v=grshfh_bpwo</a></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><a href="https://blog.makotoishida.com/2020/12/android3.html" target="_blank">次回</a>は、</div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;">6. <a href="https://blog.makotoishida.com/2020/12/android3.html" target="_blank">SQLiteデータベースの使い方</a></blockquote><br />についてまとめたいと思います。<div><br /></div><div><br /></div><div><div><b>Androidで単語帳を作ろう - 目次</b></div><div><a href="https://blog.makotoishida.com/2020/12/android1.html" target="_blank">Androidで単語帳を作ろう(1)- 画面遷移、アクティビティのライフサイクルなど</a> </div><div>Androidで単語帳を作ろう(2)- ListViewの使い方 </div><div><a href="https://blog.makotoishida.com/2020/12/android3.html" target="_blank">Androidで単語帳を作ろう(3)- SQLiteデータベースの使い方</a></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div>🍻</div></div>Makotohttp://www.blogger.com/profile/09442687702666423250noreply@blogger.comtag:blogger.com,1999:blog-37629199.post-79465723108263153042020-12-15T19:55:00.048+09:002020-12-25T11:38:02.606+09:00Androidで単語帳を作ろう(1) - 画面遷移、アクティビティのライフサイクルなど 今回から3回に分けてAndroid用の単語帳アプリを作って行こうと思います。<div><br /></div><div>ひとまず Version 0.1として最低限の機能を実装してみます。</div><div><br /></div><div><span id="docs-internal-guid-20bdcada-7fff-8185-1c04-55f7d7c20a95" style="font-size: medium;"><h3 dir="ltr" style="line-height: 1.38; margin-bottom: 4pt; margin-left: 9pt; margin-top: 16pt; padding: 0pt 0pt 0pt 13.5pt; text-indent: -13.5pt;"><span style="color: #434343; font-family: Arial; font-variant-east-asian: normal; font-variant-numeric: normal; font-weight: 400; vertical-align: baseline; white-space: pre-wrap;">Version 0.1の機能</span></h3><ul style="margin-bottom: 0px; margin-top: 0px;"><li dir="ltr" style="font-family: Arial; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: disc; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">単語一覧(英語のみ表示。覚えたものにはチェックマークを表示。)</span></p></li><li dir="ltr" style="font-family: Arial; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: disc; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">単語確認(英語・日本語を表示。「覚えた」「忘れた」ボタンでフラグ更新)</span></p></li><li dir="ltr" style="font-family: Arial; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: disc; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">単語新規登録</span></p></li><li dir="ltr" style="font-family: Arial; font-variant-east-asian: normal; font-variant-numeric: normal; list-style-type: disc; vertical-align: baseline; white-space: pre;"><p dir="ltr" role="presentation" style="line-height: 1.38; margin-bottom: 0pt; margin-top: 0pt;"><span style="font-variant-east-asian: normal; font-variant-numeric: normal; vertical-align: baseline; white-space: pre-wrap;">単語編集・削除</span></p></li></ul></span></div><div><br /></div><div><br /></div><div><br /></div><div>作成する画面は以下の4画面ですが、新規登録と編集・削除は同じアクティビティなので実質的には3画面になります。</div><div><br /></div><div><br /></div><div style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbzdyju5iXcgCX249U7YQZ1gjFmcmgg-cFlkS8sC3juzWfPfSPWKfGIoTVed02fceXxdr07Cgm5oR8h744MFe8dRFRcAyxy4im5bO8dNY9K-2hIaDASrhMBpuRm6iZf85Q6Rce/s1112/FlashcardApp+%25281%2529+%25282%2529.png"><img border="0" data-original-height="1112" data-original-width="710" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjbzdyju5iXcgCX249U7YQZ1gjFmcmgg-cFlkS8sC3juzWfPfSPWKfGIoTVed02fceXxdr07Cgm5oR8h744MFe8dRFRcAyxy4im5bO8dNY9K-2hIaDASrhMBpuRm6iZf85Q6Rce/w408-h640/FlashcardApp+%25281%2529+%25282%2529.png" width="408" /></a></div><br /><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><h3 style="text-align: left;">ソースコード</h3><div><br /></div><div><a href="https://github.com/makotoishida/flashcards-android/tree/blog-v0.1" rel="nofollow" target="_blank">https://github.com/makotoishida/flashcards-android/tree/blog-v0.1</a></div><div><br /></div><div><br /></div><div><br /></div><div>実装する上でポイントになる点としては、大まかに分けて以下の6つがあります。</div><div><br /></div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><b>1. ConstraintLayoutによるレイアウト<br /></b><b>2. ActionBar(Toolbar)の使い方<br /></b><b>3. 画面遷移の実装方法<br /></b><b>4. Activityのライフサイクル<br /></b><b>5. ListViewの使い方</b><div style="text-align: left;"><b>6. SQLiteデータベースの使い方</b></div></blockquote><div><b><br /></b></div><div><br /></div><div>以下にそれぞれのポイントについて簡単にメモして、参考になりそうなリンクなどを紹介していきます。</div><div><br /></div><div><br /></div><i>*なお、今回の説明のために参考になる資料を探していたところ、<a href="https://www.youtube.com/channel/UC5wsE9_7DFTPHMmQF3C0jMQ" rel="nofollow" target="_blank">みんなのプログラミング by Telulu LLC</a> 様の説明が大変分かりやすかったので、以下の説明で特にたくさんリンクさせていただきました。(ありがとうございます!)</i><div><br /></div><div><br /><h3>1. ConstraintLayoutによるレイアウト</h3><div><br /></div><div>従来通りのRelativeLayoutやLinerLayoutを入れ子にして画面を作成する方法でも良かったのですが、どうせなら新しいバージョンで推奨されているやり方を使おうと言うことで、ConstraintLayoutを使って画面レイアウトを作成しました。</div><div><br /></div><div>この方法だとほとんど入れ子構造を使わずにフラットな構成でウィジェット同士の相対的な位置関係を定義することでレイアウトが出来るので、慣れれば確かにこちらの方が効率が良いかも知れません。特にAndroid Studioのレイアウトエディタを使いこなせるようになればドラッグ&ドロップで画面をデザイン出来るので、かなり強力ですね。</div><div><br /></div><div><br /></div><div>下の動画を観ればおおよその使い方は分かると思います。</div><div><br /></div><div> 🎬[Android]Constraint Layoutはこれだけ知っておこう① </div><div> <a href="https://www.youtube.com/watch?v=UlcXnFdb3C0" rel="nofollow" target="_blank">https://www.youtube.com/watch?v=UlcXnFdb3C0</a></div><div><br /></div><div> 🎬[Android]Constraint Layoutはこれだけ知っておこう② </div><div> <a href="https://www.youtube.com/watch?v=gPP_ugZhvtg" rel="nofollow" target="_blank">https://www.youtube.com/watch?v=gPP_ugZhvtg</a></div><div><br /></div><div> 🎬[Android]Constraint Layoutはこれだけ知っておこう③</div><div> <a href="https://www.youtube.com/watch?v=AzkYt6W73uQ" rel="nofollow" target="_blank">https://www.youtube.com/watch?v=AzkYt6W73uQ</a></div><div><br /></div><div> 🎬[Android]ConstraintLayoutでレイアウトを作成する場合の注意点</div><div> <a href="https://www.youtube.com/watch?v=vfL7fp2RTC0" rel="nofollow" target="_blank">https://www.youtube.com/watch?v=vfL7fp2RTC0</a></div><div><div><br /></div></div><div><br /></div><div>公式のガイドも必読ですね。</div><div><br /></div><div><span> </span>Layout Editor を使用して UI を作成する | Android デベロッパー</div><div><span> </span><a href="https://developer.android.com/studio/write/layout-editor?hl=ja" rel="nofollow" target="_blank">https://developer.android.com/studio/write/layout-editor?hl=ja</a></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><h3 style="text-align: left;">2. ActionBar(Toolbar)の使い方</h3><div><br /></div><div>「戻る」「編集」「削除」などのボタンを画面上部のActionBar内に表示する方法です。</div><div><br /></div><div><br /></div><div>最近(と言ってもAndroid 5以降らしいですが)のAndroidではActionBarではなく「Toolbar」を使うほうが何かとメリットが大きいようです。</div><div><br /></div><div><div> AndroidのToolBar(新しいActionBar)メモ - Qiita </div><div> <a href="https://qiita.com/kobakei/items/f17019f8b0a88c8e57f4" rel="nofollow" target="_blank">https://qiita.com/kobakei/items/f17019f8b0a88c8e57f4</a></div><div><br /></div><div><br /></div></div><div>ただ今回のサンプルでは特に不都合は無かったのでActionBarを使って実装しました。</div><div><br /></div><div><span> </span>アプリバーの設定 | Android デベロッパー </div><div><span> </span><a href="https://developer.android.com/training/appbar/setting-up?hl=ja" rel="nofollow" target="_blank">https://developer.android.com/training/appbar/setting-up?hl=ja</a></div><div><br /></div><div><br /></div><div>また単語の新規登録と編集で同じアクティビティを使っていますが、新規登録の場合はActionBar内の「削除」メニューを非表示にする制御を行っています。</div><div><br /></div><div> OptionMenuの内容を動的に変更する - Qiita </div><div><span> </span><a href="https://qiita.com/konifar/items/b8ad0b253f173a8b76b4" rel="nofollow" target="_blank">https://qiita.com/konifar/items/b8ad0b253f173a8b76b4</a></div><div><br /></div><div><span><span> </span></span>実行時におけるメニュー項目の変更 | Android デベロッパー </div><div><span> </span><a href="https://developer.android.com/guide/topics/ui/menus?hl=ja#ChangingTheMenu" rel="nofollow" target="_blank">https://developer.android.com/guide/topics/ui/menus?hl=ja#ChangingTheMenu</a></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><h3 style="text-align: left;">3. 画面遷移の実装方法</h3><div><br /></div><div>Androidでの画面遷移を上手く制御するためには、アクティビティの「スタック」の概念をしっかりと理解しておく必要があります。</div><div><br /></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://developer.android.com/images/fundamentals/diagram_backstack.png?hl=ja" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="195" data-original-width="617" src="https://developer.android.com/images/fundamentals/diagram_backstack.png?hl=ja" /></a></div><br /><div><br /></div><div><br /></div><div><br /></div><div>たとえば、新しい画面に遷移する場合は startActivity() を使いますが、前の画面に戻るときは finish() で現在のアクティビティを「終了」する必要があります。</div><div><br /></div><div>終了せずにさらに startActivity() で元の画面に遷移してしまうと、スタックの上にさらに新しいアクティビティのインスタンスが積まれることになります。</div><div><br /></div><div><br /></div><div> 🎬[Android]今の画面を閉じて前の画面に戻る方法</div><div><span> </span><a href="https://www.youtube.com/watch?v=Rh2j2xjPncQ" rel="nofollow" target="_blank">https://www.youtube.com/watch?v=Rh2j2xjPncQ</a></div><div><br /></div><div><br /></div><div>また、複数の画面(A → B → C)を開いたあとで、(Bを飛ばして)一気に最初の画面(A)に戻りたいという場合には、ちょっと工夫が必要になります。</div><div><br /></div><div><br /></div><div><div>今回のサンプルでは、単語一覧(A) → 単語確認(B) → 単語編集(C)と遷移した状態で、単語が削除されたらBではなくAに戻すためにこの方法を使いました。</div><div><br /></div></div><div><br /></div><div>この場合は finish() で戻るのではなく startActivity() を使いますが、そのさいに特別なフラグをパラメータに追加することで C → A という画面遷移が可能になります。</div><div><br /></div><div><div><span style="font-family: arial;"> private void backToList(){</span></div><div><span style="font-family: arial;"> Intent intent = new Intent(getApplicationContext(), WordListActivity.class);</span></div><div><span style="color: #2b00fe; font-family: arial;"> intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);</span></div><div><span style="color: #2b00fe; font-family: arial;"> intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);</span></div><div><span style="font-family: arial;"> startActivity(intent);</span></div><div><span style="font-family: arial;"> }</span></div></div><div><br /></div><div><br /></div><div>詳しくは下のサイトを参照してください。</div><div><br /></div></div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><div><div>最初のActivityに戻る - Qiita </div></div><div><div><a href="https://qiita.com/naoty_k/items/f733ff8a69a141331bba" rel="nofollow" target="_blank">https://qiita.com/naoty_k/items/f733ff8a69a141331bba</a></div></div></blockquote><div><div><br /></div><div><br /></div><div><br /></div><div><h4 style="text-align: left;">次の画面にデータを渡す</h4></div><div><br /></div><div>単語一覧画面から単語確認画面に遷移するケースでは、画面間で「どの単語がタップされたのか」という情報を受け渡す必要が出てきます。これは、インテントの putExtra() メソッドを使って渡したいデータをインテント内に詰め込むことで実現出来ます。</div><div><br /></div><div><div><span style="font-family: arial;"> Intent intent = new Intent(getApplicationContext(), WordViewActivity.class);</span></div><div><span style="color: #2b00fe; font-family: arial;"> <span>intent.putExtra("_id", word._id);</span></span></div><div><span style="font-family: arial;"> startActivity(intent);</span></div></div><div><br /></div><div><br /></div><h4 style="text-align: left;">渡されたデータを取得する</h4><div><br /></div><div>遷移先の画面では、onCreate または onResume でインテントの getIntExtra(), getStringExtra() などのメソッドを使って詰め込まれたデータを取り出します。</div><div><br /></div><div><br /></div></div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><div><div><div><span style="font-family: arial;"> protected void onResume() {</span></div></div></div><div><div><div><span style="font-family: arial;"> super.onResume();</span></div></div></div><div><div><div><span style="font-family: arial;"><br /></span></div></div></div><div><div><div><span style="font-family: arial;"> // 渡されたインテントから単語IDを得る。</span></div></div></div><div><div><div><span style="font-family: arial;"> Intent intent = getIntent();</span></div></div></div><div><div><div><span style="font-family: arial;"> mWordId = <span style="color: #2b00fe;">intent.getIntExtra("_id", 0)</span>;</span></div></div></div><div><div><div><span style="font-family: arial;"><br /></span></div></div></div><div><div><div><span style="font-family: arial;"> // 指定された単語のデータをデータベースから取得して表示する。</span></div></div></div><div><div><div><span style="font-family: arial;"> loadWord(mWordId);</span></div></div></div><div><div><div><span style="font-family: arial;"> }</span></div></div></div></blockquote><div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><h3 style="text-align: left;">4. Activityのライフサイクル</h3><div><br /></div><div><div><a href="https://github.com/makotoishida/flashcards-android/blob/blog-v0.1/app/src/main/java/com/example/flashcards/WordViewActivity.java" target="_blank">サンプルの WordViewActivity</a> では、上に書いた「元の画面から渡されたデータを取り出す」という処理を onResume でおこなっていますが、これは単語一覧画面から開かれたときだけでなく単語編集画面から戻ってきた場合にも単語の表示を更新したい(=編集後の最新の値を反映したい)ためです。</div><div><br /></div><div><br /></div><div>onCreate はアクティビティが生成されたタイミングでしか呼ばれませんが、onResume はアクティビティがスタックの先頭に来て画面に表示される度に毎回呼ばれます。</div><div><br /></div></div><div><br /></div><div>Android用のアプリケーションを開発する上では、このようにアクティビティのライフサイクルを理解することが非常に重要になります。</div><div><br /></div><div><br /></div><div>これについては下の動画が大変分かりやすいのでオススメです。</div><div><br /></div><div> 🎬[Android]画面(Activity)のライフサイクルとは</div><div><span> </span><a href="https://www.youtube.com/watch?v=ARsB5mLQUHQ" rel="nofollow" target="_blank">https://www.youtube.com/watch?v=ARsB5mLQUHQ</a></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div>今回は、</div></div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;">1. ConstraintLayoutによるレイアウト<br />2. ActionBar(Toolbar)の使い方<br />3. 画面遷移の実装方法<div style="text-align: left;"><div><div>4. Activityのライフサイクル</div></div></div></blockquote><div><div><br /></div><div>について書きました。</div><div><br /></div><div><a href="https://blog.makotoishida.com/2020/12/android2.html" target="_blank">次回</a>は、</div></div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><div><div><div>5. <a href="https://blog.makotoishida.com/2020/12/android2.html" target="_blank">ListViewの使い方</a></div></div></div></blockquote><div><div><br /></div><div>についてまとめたいと思います。</div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><div><b>Androidで単語帳を作ろう - 目次</b></div><div>Androidで単語帳を作ろう(1)- 画面遷移、アクティビティのライフサイクルなど </div><div><a href="https://blog.makotoishida.com/2020/12/android2.html" target="_blank">Androidで単語帳を作ろう(2)- ListViewの使い方</a> </div><div><a href="https://blog.makotoishida.com/2020/12/android3.html" target="_blank">Androidで単語帳を作ろう(3)- SQLiteデータベースの使い方</a></div><div><br /></div><div><br /></div></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div>🍻</div></div>Makotohttp://www.blogger.com/profile/09442687702666423250noreply@blogger.comtag:blogger.com,1999:blog-37629199.post-47161895054188165322020-11-27T12:52:00.012+09:002021-01-26T07:18:57.956+09:00Mac上のChromeで自己署名のサーバー証明書を使ったサイトが開けないときある開発中のWebサイトがあるのですが、最近開発マシンを変えたところ、今までGoogle Chromeでアクセス出来ていたのに急に出来なくなりました。<div><br /></div><div>表示されるエラーメッセージはこんな感じです。</div><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-dkz9n048vms/X8B0qzKKR8I/AAAAAAAAc40/abXIgToh48AR_Hj64v1QW_5lcYjgFFaQwCLcBGAsYHQ/s1580/Screen%2BShot%2B2020-11-26%2Bat%2B5.31.06%2BPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1580" data-original-width="1360" height="400" src="https://1.bp.blogspot.com/-dkz9n048vms/X8B0qzKKR8I/AAAAAAAAc40/abXIgToh48AR_Hj64v1QW_5lcYjgFFaQwCLcBGAsYHQ/w344-h400/Screen%2BShot%2B2020-11-26%2Bat%2B5.31.06%2BPM.png" width="344" /></a></div><br /><div>今までは、このメッセージが出ても下の方に「危険を承知で開く」という意味のリンクがあってそこをクリックすれば続行出来ました。</div><div><br /></div><div><br /></div><div>試しに古い方の開発マシンからやってみると、確かにそのようになっていてそちらでは開けます。</div><div><br /></div><div><br /></div><div>マシンが変わっただけで開けなくなったということは、何か新しいマシン上で設定が足りていないのかな、と思って調べてみたところ、下の方法で解決したのでメモしておきます。</div><div><br /></div><div><br /></div><div>Chromeの「Settings」メニューから、「Privacy and Security」→ 「Security」を開きます。</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-crRiATcUZjc/X8B2MunmOPI/AAAAAAAAc5A/Lom_TQOS6fke8cWBuh6YT7Mlzj70wdLtQCLcBGAsYHQ/s1992/Screen%2BShot%2B2020-11-26%2Bat%2B5.43.46%2BPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1120" data-original-width="1992" src="https://1.bp.blogspot.com/-crRiATcUZjc/X8B2MunmOPI/AAAAAAAAc5A/Lom_TQOS6fke8cWBuh6YT7Mlzj70wdLtQCLcBGAsYHQ/s320/Screen%2BShot%2B2020-11-26%2Bat%2B5.43.46%2BPM.png" width="320" /></a></div><br /><div><br /></div><div>その中の「Manage certificates」メニューをクリックすると、Appleの「Keychain Access」アプリが開きます。(直接このアプリを開いた方が早かった。。。)</div><div><br /></div><div><br /></div><div>Keychain AccessアプリのCertificatesの中に該当のサイトの証明書があればそれをダブルクリックします。</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1ux3EDvqlPQ_IfcPXcT_mUSXQTF0GZTOH6Oslyys2-q8ZP3sxmO-wr48FoIxW2gqzujCLJbsjItjmhMc8OGj2AXM7OnqPQkpuihhn61AQJiZ2XpsF3TK-spXj6lJ_dPPn7fm7/s1064/Screen+Shot+2020-11-26+at+5.48.19+PM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="712" data-original-width="1064" height="268" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi1ux3EDvqlPQ_IfcPXcT_mUSXQTF0GZTOH6Oslyys2-q8ZP3sxmO-wr48FoIxW2gqzujCLJbsjItjmhMc8OGj2AXM7OnqPQkpuihhn61AQJiZ2XpsF3TK-spXj6lJ_dPPn7fm7/w400-h268/Screen+Shot+2020-11-26+at+5.48.19+PM.png" width="400" /></a></div><br /><div><br /></div><div>証明書の詳細が表示されるので、「Always Trust」にしてこの画面を閉じればOKです。</div><div><br /></div><div><br /></div><div>この後、ブラウザで該当のURLを開くと、いつも通りにセキュリティの警告が出ますが、一番下の「危険を承知で開く」という意味のリンクをクリックすればサイトが開きます。</div><h4 style="text-align: left;"><br /></h4><div><br /></div><div><b>追記:</b></div><div>デバッグ用にローカル環境(localhost)でサイトを動かしている場合は上の方法では上手く行かないかも知れません。その場合は、下の方法を試してみてください。</div><div><br /></div><div><br /></div><div><a href="https://qiita.com/TK-C/items/13efa5a45beb6de29134" rel="nofollow" target="_blank">localhostで開発中にGoogle Chromeで「この接続はプライバシーが保護されません」が出たときの対処法【NET::ERR_CERT_INVALID】 - Qiita</a> </div><div><br /></div><div><br /></div><div><a href="https://stackoverflow.com/questions/58802767/no-proceed-anyway-option-on-neterr-cert-invalid-in-chrome-on-macos?rq=1" rel="nofollow" target="_blank">No “Proceed Anyway” option on NET::ERR_CERT_INVALID in Chrome on MacOS - Stack Overflow</a> </div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /><div><br /></div><div><br /></div><div>🍻 </div></div>Makotohttp://www.blogger.com/profile/09442687702666423250noreply@blogger.comtag:blogger.com,1999:blog-37629199.post-34888367658878734902020-10-31T09:29:00.013+09:002020-12-27T18:34:27.308+09:00Windows, Linux, Macで英語キーボードの左右ALTキーで日本語入力ON/OFFを切り替える<h3 style="text-align: left;">一つのキーでトグルするのではなく別々のキーを使うことのメリット</h3><div><br /></div><div>下で紹介している alt-ime-ahk の作者さんの<a href="https://www.karakaram.com/alt-ime-on-off/" rel="nofollow" target="_blank">ブログ</a>に書かれているとおりです。</div><div><br /></div><div><i></i><blockquote><i>”これの何が便利かというと、現在の IME の状態が何であろうと、日本語を入力したいときは「かな」英語を入力したいときは「英数」を押せばよい所です。いちいち IME の状態を気にしなくてもよいので、とても楽なのです。”</i></blockquote></div><div><br /></div><div><br /></div><div><b>「<span style="color: #2b00fe; font-size: medium;">左でOFF、右でON</span>」</b>これに慣れるともうトグル方式には戻れません。</div><div><br /></div><div><br /></div><div>ちなみに、「<b>英語配列</b>」のキーボードでの話です。</div><div><br /></div><div><br /></div><div><br /></div><h3 style="text-align: left;">Windowsでの設定 </h3><div><br /></div><div>「<a href="https://github.com/karakaram/alt-ime-ahk" rel="nofollow" target="_blank">alt-ime-ahk</a>」を使います。</div><div><br /></div><div>Win+Rキーから「shell:startup」と入力して開くフォルダにExeファイルへのショートカットを作成しておけば、ログイン時に自動的に起動してくれます。</div><div><br /></div><div>この設定をしてから、英語キーボードで毎回「ALT+`」で切り替えていたのが「どれだけ時間の無駄だったのか」を痛感しました。</div><div><br /></div><div><br /></div><div><br /></div><h3 style="text-align: left;">Linuxでの設定</h3><div><br /></div><div>Mozcでは上手く行かないので、「Fcitx」で設定します。詳細は<a href="https://qiita.com/ys-0-sy/items/b969c3224f97a0002829" rel="nofollow" target="_blank">こちら</a>。</div><div><br /></div><div><br /></div><div><br /></div><h3 style="text-align: left;">Macでの設定</h3><div><br /></div><div>「<a href="https://karabiner-elements.pqrs.org/" rel="nofollow" target="_blank">Karabiner-Elements</a>」を使います。</div><div><br /></div><div><a href="https://qiita.com/daichi87gi/items/ded35e9d9a54c8fcb9d6" rel="nofollow" target="_blank">普通に設定</a>すると左右のCommandキーで切り替え可能になります。</div><div><br /></div><div>私はさらにCommandキーに加えて左右の<b>Optionキー</b>でも切り替わるように設定を追加して使ってい<strike><span style="color: red;">ます。</span></strike> ました。</div><div><br /></div><div>こうするとWindowsやLinuxと共通になるので便利<span style="color: red;"><strike>です</strike>。</span>でした。</div><div><br /></div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-kmeWkc6ZPxo/X5yvS1TUy_I/AAAAAAAAcqc/rHQrf0M5nDMjft2YTfe_b35FWhJ4dox-gCLcBGAsYHQ/s1310/Screen%2BShot%2B2020-10-30%2Bat%2B2.14.32%2BPM.png" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="1272" data-original-width="1310" height="389" src="https://1.bp.blogspot.com/-kmeWkc6ZPxo/X5yvS1TUy_I/AAAAAAAAcqc/rHQrf0M5nDMjft2YTfe_b35FWhJ4dox-gCLcBGAsYHQ/w400-h389/Screen%2BShot%2B2020-10-30%2Bat%2B2.14.32%2BPM.png" width="400" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Karabiner-Elementsの設定ファイルを開いて、left_command, right_commandをleft_option, right_optionに変更すればOptionキーで切り替え可能に。<br /></td></tr></tbody></table><br /><br /><div><span style="color: #2b00fe;">その後、 MacでOptionキーのアサインを変えてしまうと「<b>Optionを押しながらメニューをクリック</b>」した時の挙動が変わってしまうことに気付いたので、やっぱりMacではこの設定はやめて<b>左右Commandキー</b>で切り替えることにしました。</span><span>Windos, LinuxではALTキー、MacではCommandキーという点が異なりますが、「<b>左でOFF、右でON</b>」というのは共通しているので、ひとまずこれで良しとしています。</span></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div>🍺</div>Makotohttp://www.blogger.com/profile/09442687702666423250noreply@blogger.comtag:blogger.com,1999:blog-37629199.post-10112695929561507952020-10-30T10:40:00.010+09:002020-10-31T04:46:48.792+09:00データベースとTypeScriptの型定義を同期したいときに便利そうなライブラリ<div>下の動画を見て、やはりデータベース ⇔ バックエンド ⇔ フロントエンドの間でスキーマ定義が半自動的に同期される仕組みがあるというのは便利だな〜と思ったので、そのために使えそうなライブラリを調べました。</div><div><br /></div><div><br /></div><div>===</div><iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" frameborder="0" height="315" src="https://www.youtube.com/embed/GrnBXhsr0ng" width="560"></iframe> <div>===</div><div><br /></div><div><br /></div><div><div>目標は、データベースを変更 → TypeScriptの型定義が半自動で更新される → バックエンドとフロントエンドの両方のプロジェクトで型定義が共有される、という状態にすることです。</div><div><br /></div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-PHrEOaO6Ozg/X5trGgvwmGI/AAAAAAAAcps/yUq9p1yb5SQJxpjpR8Hc7DKH4m638mFQACLcBGAsYHQ/s772/DB-TypeDefinition.png" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="442" data-original-width="772" height="229" src="https://1.bp.blogspot.com/-PHrEOaO6Ozg/X5trGgvwmGI/AAAAAAAAcps/yUq9p1yb5SQJxpjpR8Hc7DKH4m638mFQACLcBGAsYHQ/w400-h229/DB-TypeDefinition.png" width="400" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">バックエンドのDBアクセス、REST APIから、フロントエンドのReactコンポーネントのPropsまで、アプリケーション全体で共通の型定義を使いたい<br /></td></tr></tbody></table><br /><div><br /></div><div><br /></div></div><div><br /></div><div><br /></div><div>1. <a href="https://github.com/SweetIQ/schemats" rel="nofollow" target="_blank">schemats</a></div><div><br /></div><div>動画で使われているのは、<a href="https://github.com/SweetIQ/schemats" rel="nofollow" target="_blank">schemats</a> というライブラリでした。これは、データベースからテーブルのスキーマを読み取って、TypeScriptの型定義を生成してくれるというもののようです。</div><div><br /></div><div>サンプルコードや<a href="https://www.cs.mcgill.ca/~mxia3/2016/11/18/Statically-typed-PostgreSQL-queries-and-typescript-schemats/" rel="nofollow" target="_blank">こちらのブログ記事</a>を見ても分かるとおり、このライブラリは特にORMのようなものを介さずに自分で直接SQLクエリーを書いてデータベースにアクセスしたいという場合に向いているようです。</div><div><br /></div><div><br /></div><div><br /></div><div><div><div>2. <a href="http://knexjs.org/" rel="nofollow" target="_blank">Knex.js</a> / <a href="https://github.com/rmp135/sql-ts" rel="nofollow" target="_blank">sql-ts</a> </div><div><br /></div><div>Knex.js という軽量なDBアクセスライブラリを使う場合は、sql-ts もしくは上に挙げた schemats を利用して生成した型定義を使うことができそうです。</div><div><br /></div><div><br /></div><div><br /></div></div></div><div>3. <a href="https://www.prisma.io/" rel="nofollow" target="_blank">Prisma</a></div><div><br /></div><div>専用のスキーマ定義ファイルからTypeScriptの型定義を生成します。</div><div><br /></div><div>このPrismaスキーマをデータベースから自動生成するワークフローと、まず自分でPrismaスキーマを書いて、それをマイグレーションでデータベースに反映するワークフローの2種類をサポートしているそうです。</div><div><br /></div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody><tr><td style="text-align: center;"><a href="https://i.imgur.com/ToNkpb2.png" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="356" data-original-width="800" height="178" src="https://i.imgur.com/ToNkpb2.png" width="400" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">DBからPrismaスキーマを生成<br /></td></tr></tbody></table><br /><div><div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"> </blockquote></blockquote><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://i.imgur.com/OImder6.png" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="421" data-original-width="800" height="211" src="https://i.imgur.com/OImder6.png" width="400" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">PrismaスキーマからDBに反映<br /></td></tr></tbody></table><br /><div><br /></div><div>ただ、<a href="https://github.com/prisma/prisma-examples" rel="nofollow" target="_blank">こちらのNext.jsでのサンプル</a>を見た感じでは、データベース⇔バックエンドの間では型定義の同期が取れていますがフロントエンド側まではそれが共有されていません。例えば個々のReactコンポーネントのPropsについてはそれぞれに型定義をおこなっているようです。フロントエンド側にまで型定義を共有出来る方法があるのかどうかについてはよく分かりませんでした。</div></div><div><br /></div><div><br /></div><div><br /></div><div><div>4. <a href="https://typeorm.io/#/" rel="nofollow" target="_blank">TypeORM</a> / <a href="https://github.com/Kononnable/typeorm-model-generator" rel="nofollow" target="_blank">typeorm-model-generator</a></div></div><div><br /></div><div>TypeScriptを使う場合のORMライブラリとしてかなり人気があると思われるTypeORMですが、別途 typeorm-model-generator というCLIツールを組み合わせるとデータベースからTypeORM用のモデルクラスを生成することが出来るようです。</div><div><br /></div><div><br /></div><div><br /></div><div>5. <a href="https://sequelize.org/" rel="nofollow" target="_blank">Sequelize</a> / <a href="https://github.com/spinlud/sequelize-typescript-generator" rel="nofollow" target="_blank">sequelize-typescript-generator</a></div><div><br /></div><div>Node.js用のORMとしておそらく最も使われているSequelizeですが、これ向けにも sequelize-typescript-generator というCLIツールが見つかりました。Sequelize を採用することが決まっている場合は、こちらの組み合わせも良いかも知れません。 ただ、Sequlize向けに生成されたモデルクラスをそのままフロントエンド側で参照してReactコンポーネントなどで使えるかというと、それは厳しいのではないかと思います。</div><div><br /></div><div><br /></div><div><br /></div><h3 style="text-align: left;"><b>考察</b></h3><div><br /></div><div>「DBから生成した型定義をバックエンドからフロントエンドまで共通で使いたい」と書きましたが、結局よく考えてみると、</div><div><br /></div></div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><div><div style="text-align: left;">①バックエンド側でDBアクセスをするための型(テーブル単位)</div></div><div><div style="text-align: left;">②APIのインターフェースとしての型(ユースケース単位)</div></div><div><div style="text-align: left;">③フロントエンドのコンポーネント間でやり取りするための型(UI部品単位)</div></div></blockquote><div><div><br /></div><div>はそれぞれに用途が異なるので完全に同じものを使い回すというようなことは現実的ではなさそうです。</div><div><br /></div><div>ただ、①②③の中でもカラム単位で見れば共通の定義を抽出して参照することは可能ではないかと思います。</div><div><br /></div><div>例えば冒頭の動画で紹介されているように「通知メールの許可設定」を表すカラムについて、当初 Boolean だったものが後から Number に変更されたというようなケースでは、カラム単位で共通の型定義を参照していれば、一箇所が(コマンド一つで半自動的に)変更されれば(バックエンド、フロントエンド含めて)それを参照している全ての箇所がコンパイル時にエラーとなって容易に修正出来るようになるというイメージです。</div><div><br /></div><div>このような用途で考えると、今回調べたライブラリの中では schemats は一度試してみる価値があるかも知れないと思いました。</div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div>🍻</div></div>Makotohttp://www.blogger.com/profile/09442687702666423250noreply@blogger.comtag:blogger.com,1999:blog-37629199.post-67750116476585728392020-10-28T12:26:00.010+09:002020-10-28T13:51:05.956+09:00MacのKarabiner-Elementsで「PC−Style Home/End」を有効にしたときにChrome Remote Desktopを除外する方法<div>数年前からRealforceのキーボードをMacBook Proにつないで使っています。とても打ちやすくて快適なのですが、長い間解決できずに困っている問題がありました。</div><div><br /></div><div><br /></div><div>それは、HomeキーとEndキーで行頭・行末にカーソルを移動するために <a href="https://karabiner-elements.pqrs.org/" rel="nofollow" target="_blank">Karabiner-Elements</a> の「<a href="https://ke-complex-modifications.pqrs.org/#pc_shortcuts" rel="nofollow" target="_blank">PC-Sylte Home/End</a>」という設定を有効にすると、Google Chrome Remote Desktop を使ってWindowsマシンを操作しているときに、Homeが「Windows+左矢印キー」、Endが「Windows+右矢印キー」として伝わってしまい、アプリケーションのウィンドウがいきなり画面の左半分にリサイズされたり右半分にリサイズされたりしてしまう、というものです。</div><div><br /></div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglArue4up-cpOffsoUpmxLcY_IN23YoI0_9f25SsFVgoopQeHq6Fap6Go42nVDdjTGUUbhQhmoQdXUKO4FvVvwjxUFCyNeyndboQC2LJI7Khg2MR6IfWFAb0o8FG2pf7eFkPDC/s2224/Screen+Shot+2020-10-27+at+3.22.19+PM.png" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="1368" data-original-width="2224" height="246" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEglArue4up-cpOffsoUpmxLcY_IN23YoI0_9f25SsFVgoopQeHq6Fap6Go42nVDdjTGUUbhQhmoQdXUKO4FvVvwjxUFCyNeyndboQC2LJI7Khg2MR6IfWFAb0o8FG2pf7eFkPDC/w400-h246/Screen+Shot+2020-10-27+at+3.22.19+PM.png" title="Karabiner-Elementsの設定" width="400" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Karabiner-Elementsの設定で「PC-Style Home/End」を追加<br /></td></tr></tbody></table><br /><div><br /></div><div><br /></div><div><br /></div><div>Microsoft Remote Desktop などではこのような問題は起こりません。「<b>Chrome Remote Desktop</b>」 を使ったときにだけ起きる問題です。</div><div><br /></div><div>これは Karabiner-Elements の「PC-Sylte Home/End」設定の「除外対象(frontmost_application_unless)」に、Chrome Remote Desktop が含められていないのが原因のようです。</div><div><br /></div><div><br /></div><div>どうやって Chrome Remote Desktop を除外対象に含めれば良いのか分からなかったのですが、今日は長い間避けて通ってきたこの問題にようやく時間をとって真剣に向き合うことにしました。</div><div><br /></div><div><br /></div><div>調査 & 試行錯誤すること1時間、ついに解決策を発見したのでメモしておきます。</div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-yKr6Kr7QBrs/X5jhOjySrSI/AAAAAAAAcoU/4RPvTgec_O4A6qKRMT6mQDzfOhS8eGlnQCPcBGAYYCw/s2224/Screen%2BShot%2B2020-10-27%2Bat%2B3.23.05%2BPM.png" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="1368" data-original-width="2224" height="246" src="https://1.bp.blogspot.com/-yKr6Kr7QBrs/X5jhOjySrSI/AAAAAAAAcoU/4RPvTgec_O4A6qKRMT6mQDzfOhS8eGlnQCPcBGAYYCw/w400-h246/Screen%2BShot%2B2020-10-27%2Bat%2B3.23.05%2BPM.png" width="400" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">設定のJSONファイルをエディタで開く<br /></td></tr></tbody></table><br /><div><br /></div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://1.bp.blogspot.com/-V5P9q3B7evI/X5jiSKAAA-I/AAAAAAAAcow/RxJw_ME7BV4uUetY58lA7lfBzQBEnQFCQCLcBGAsYHQ/s1366/Screen%2BShot%2B2020-10-27%2Bat%2B3.25.09%2BPM.png" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="1366" data-original-width="884" height="400" src="https://1.bp.blogspot.com/-V5P9q3B7evI/X5jiSKAAA-I/AAAAAAAAcow/RxJw_ME7BV4uUetY58lA7lfBzQBEnQFCQCLcBGAsYHQ/w259-h400/Screen%2BShot%2B2020-10-27%2Bat%2B3.25.09%2BPM.png" width="259" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">Chrome Remote Desktopを除外する記述を追加</td></tr></tbody></table><br /><br /><div>"type": "frontmost_application_unless", </div><div>の下の</div><div> "bundle_identifiers": [ ... ] </div><div>の配列に</div><div><div><blockquote><span style="color: #2b00fe;"> "^com\\.google\\.Chrome\\.app\\.*",</span></blockquote></div></div><div>の行を追加すればOKです! </div><div><br /></div><div>(HomeキーとEndキーでそれぞれに設定があるので2箇所とも追加します。)</div><div><br /></div><div><br /></div><div>検索して見つけた一部のサイトでは、</div><div><br /></div><div> "^com\\.google\\.Chrome\\.app",</div><div><br /></div><div>と書かれていたのですが、これだと上手く行かず、末尾に「\\.*」を追加することで正しく除外されるようになりました。</div><div><br /></div><div><br /></div><div>ちなみに末尾が「\\.*」だとChrome Remote Desktop だけではなく「全てのChromeアプリ」が除外対象になると思います。</div><div><br /></div><div><br /></div><div>もし不都合がある場合はそれぞれのChromeアプリ固有のIDを調べて個々に指定する必要があります。</div><div><br /></div><div><br /></div><div><br /></div><div>アプリ固有のBundle Identifierの値は、Karabiner-Elementsに付属の「<a href="https://karabiner-elements.pqrs.org/docs/json/complex-modifications-manipulator-definition/conditions/frontmost-application/" rel="nofollow" target="_blank">EventViewer</a>」を起動すれば簡単に調べることが出来ます。</div><div><br /></div><div><br /></div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiyBck6ffgkj1uvI4A4CnUQITrLutXnoHa4izozaT4eZQ2i0m31r93sQLgNV7v4JYMeWgHM5hJt2jhxrqxxExna7zW4BCkryF7sGD_gnsTG4DDhswzZzzS8DYQHsmvyDL09OoU7/s1972/Screen+Shot+2020-10-27+at+5.02.50+PM.png" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="1368" data-original-width="1972" height="278" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiyBck6ffgkj1uvI4A4CnUQITrLutXnoHa4izozaT4eZQ2i0m31r93sQLgNV7v4JYMeWgHM5hJt2jhxrqxxExna7zW4BCkryF7sGD_gnsTG4DDhswzZzzS8DYQHsmvyDL09OoU7/w400-h278/Screen+Shot+2020-10-27+at+5.02.50+PM.png" width="400" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;">EventViewer で Bundle Identifierを調べる<br /></td></tr></tbody></table><br /><div class="separator" style="clear: both; text-align: center;"><br /></div><div><br /></div>最近は Microsoft のRemote Desktop よりも Chrome Remote Desktop を使う機会の方が多くなってきているので、これでさらに快適にリモート接続出来そうです!<br /><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div>🍺 </div>Makotohttp://www.blogger.com/profile/09442687702666423250noreply@blogger.comtag:blogger.com,1999:blog-37629199.post-3999526690138888512020-10-01T09:22:00.003+09:002020-10-01T09:28:32.962+09:00ReactによるWebアプリ開発を体系的に学べるコース「Epic React」を購入!<h2 style="text-align: left;"><b>購入したコース</b></h2><div><br /></div><div><a href="https://epicreact.dev/" rel="nofollow" target="_blank">Get Really Good at React | Epic React by Kent C. Dodds</a> </div><div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDcEpdRpx4R893wJ5fGnR3_8ZQEOQT16cjfEZSxZWEkqPb20g7rqeA76jYsO7bif8B_ia0RixWvck8rIxd__DJ3NyYCRmF4RWSs_goX-SqErxsd2IeaPDCIOgymu7eZljmP1tI/s2274/Screen+Shot+2020-09-30+at+11.46.19+AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1306" data-original-width="2274" height="230" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhDcEpdRpx4R893wJ5fGnR3_8ZQEOQT16cjfEZSxZWEkqPb20g7rqeA76jYsO7bif8B_ia0RixWvck8rIxd__DJ3NyYCRmF4RWSs_goX-SqErxsd2IeaPDCIOgymu7eZljmP1tI/w400-h230/Screen+Shot+2020-09-30+at+11.46.19+AM.png" width="400" /></a></div><br /><div><b>内容</b></div><div><ul style="text-align: left;"><li>React Fundamentals</li><li>React Hooks</li><li>Advanced React Hooks</li><li>Advanced React Patterns</li><li>React Performance</li><li>Testing React Apps</li><li>React Suspense</li><li>Build an Epic React App</li><li>Epic React Expert Interviews</li></ul></div><div><br /></div><div><br /></div><div><br /></div><h2 style="text-align: left;">購入の動機</h2><div><br /></div><div>以前から<a href="https://egghead.io/courses/the-beginner-s-guide-to-react" target="_blank">Egghead.ioの動画</a>と<a href="https://kentcdodds.com/blog/" target="_blank">ブログ</a>でKent C. DoddsさんのReactやJavascriptに関する解説を読んだり観たりしていて、とても参考になっていたため。</div><div><br /></div><div>今回のコースはそのKentさんが何年にもわたって数々の有料セミナーなどで教えてきた経験を元に作成された、渾身の作ということなので、まず期待はずれになることはないだろうと確信が持てました。</div><div><br /></div><div><br /></div><h2 style="text-align: left;">インストラクター</h2><div><br /></div><div><a href="https://egghead.io/instructors/kent-c-dodds" target="_blank">https://egghead.io/instructors/kent-c-dodds</a></div><div><br /></div><div>元PayPalのフロントエンド開発者で、Reactのテスト用ライブラリとして人気の「<a href="https://github.com/testing-library/react-testing-library" rel="nofollow" target="_blank">React Testing Library</a>」の作者でもあります。</div><div><br /></div><div><a href="https://kentcdodds.com/blog/2010s-decade-in-review" target="_blank">ブログ</a>を読むと、初めてソフトウェア開発者として職を得たきっかけ、Facebookからのオファーを断ってPayPalに入った経緯、PayPalでの仕事内容、AngularからReactに軸足を移した経緯や、人に教えることへの情熱などがよくわかります。</div><div><br /></div><div><br /></div><div><br /></div><h2 style="text-align: left;">Egghead.ioでおすすめのコース</h2><div><br /></div><div>Kentさんのコースに興味を持たれた方は、まずはEgghead.ioで下の動画レッスンなどを視聴してみてはいかがでしょうか。英語のリスニング練習にも最適です! 😀</div><div><br /></div><div>React Tutorial for Beginners</div><div><a href="https://egghead.io/courses/the-beginner-s-guide-to-react" target="_blank">https://egghead.io/courses/the-beginner-s-guide-to-react</a></div><div><br /></div><div>Collection - Testing JavaScript with Jest</div><div><a href="https://egghead.io/playlists/testing-javascript-with-jest-a36c4074" target="_blank">https://egghead.io/playlists/testing-javascript-with-jest-a36c4074</a></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div> </div></div>Makotohttp://www.blogger.com/profile/09442687702666423250noreply@blogger.comtag:blogger.com,1999:blog-37629199.post-89769657203388814862020-07-30T06:19:00.002+09:002020-07-30T06:21:51.922+09:00Parcelでasync/awaitを使うと「regeneratorRuntime is not defined」エラーが出る場合の対処法<div><a href="https://parceljs.org/" target="_blank">Parcel</a> を使ってJavaScriptアプリケーションを書いているときに、async/awaitを使おうとすると下のエラーが出ました。</div><div><br /></div><div><blockquote><font color="#ff0000">Uncaught ReferenceError: regeneratorRuntime is not defined</font></blockquote></div><div><br /></div><div>調べたところ、次のサイトに解決策が載っていました。</div><div><br /></div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"><div style="text-align: left;"><a href="https://flaviocopes.com/parcel-regeneratorruntime-not-defined/" target="_blank">Parcel, how to fix the `regeneratorRuntime is not defined` error</a> </div></blockquote><div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"><div style="text-align: left;"><br /></div></blockquote></div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"><div style="text-align: left;"><a href="https://github.com/parcel-bundler/parcel/issues/871" target="_blank">regeneratorRuntime is not defined</a> </div></blockquote><div><br /></div><div><br /></div><div><br /></div><h3 style="text-align: left;">対処法1</h3><div><br /></div><div>ブラウザを比較的新しいものだけに限定して構わないのであれば、次の方法が簡単です。</div><div><br /></div><div>package.jsonに以下を追記します。</div><div><br /></div><div><div></div></div><blockquote><div><div> "browserslist": [</div><div> "since 2017-06"</div><div> ]</div></div><div></div></blockquote><div>これでasync/awaitを使ってもエラーが出なくなります。</div><div><br /></div><div><br /></div><div><br /></div><h3 style="text-align: left;">対処法2</h3><div><br /></div><div>もう一つの対処法としては、「regenerator-runtime」パッケージをインストールするというものがあります。</div><div><br /></div><div>> npm install regenerator-runtime</div><div><br /></div><div>でインストールしておき、index.jsなどから</div><div><br /></div><div><div>import 'regenerator-runtime/runtime';</div></div><div><br /></div><div>として読み込みます。</div><div><br /></div><div>バンドルサイズが25KBほど増えてしまうようですが、こちらの方法でもエラーが出なくなります。古いブラウザもサポートする必要がある場合はこちらを使うことになると思います。</div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div> .<div><br /></div><div><br /></div>Makotohttp://www.blogger.com/profile/09442687702666423250noreply@blogger.comtag:blogger.com,1999:blog-37629199.post-11483758330335281972020-06-16T13:11:00.008+09:002020-06-16T13:57:54.376+09:00データベース不要! ReactとGoogle Drive APIで任意のデータを保存する<a href="https://blog.makotoishida.com/2020/06/reactspagoogle.html" target="_blank">前回</a>はReactアプリにGoogle認証を組み込む方法を試しました。<div><br /></div><div>今回はそのコードを使いながら、さらに<a href="https://developers.google.com/drive/api/v3/quickstart/js" target="_blank">Google Drive API</a>を使って任意のデータをログインしているユーザーのGoogle Driveに保存することに挑戦してみました。<div><div><br /></div><div><br /></div><div>完全にブラウザのみで動作するReactアプリケーションなので、バックエンドサーバは不要です。Netlifyなど静的サイトのホスティングサービスを使ってデプロイすることが可能です。</div><div><br /></div><div><br /></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-W5DEURID0SI/Xug6Dn8w_jI/AAAAAAAAa_c/mL-veIKxgfAyS3cTb6f5swUL8_Zf9FLxACK4BGAsYHg/s1438/Screen%2BShot%2B2020-06-15%2Bat%2B5.15.11%2BPM.png" style="margin-left: 1em; margin-right: 1em;"><img alt="Screen shot on a PC" border="0" data-original-height="988" data-original-width="1438" height="275" src="https://1.bp.blogspot.com/-W5DEURID0SI/Xug6Dn8w_jI/AAAAAAAAa_c/mL-veIKxgfAyS3cTb6f5swUL8_Zf9FLxACK4BGAsYHg/w400-h275/Screen%2BShot%2B2020-06-15%2Bat%2B5.15.11%2BPM.png" width="400" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-Bxs387_5PF0/Xug6KudWrQI/AAAAAAAAa_o/3jvf1Ok-0gI3xB3ZIAUZbiXJtg_WYON9QCK4BGAsYHg/s1136/localhost_3000_%2528iPhone%2B5_SE%2529.png" style="margin-left: 1em; margin-right: 1em;"><img alt="Screen shot on a phone" border="0" data-original-height="1136" data-original-width="640" height="320" src="https://1.bp.blogspot.com/-Bxs387_5PF0/Xug6KudWrQI/AAAAAAAAa_o/3jvf1Ok-0gI3xB3ZIAUZbiXJtg_WYON9QCK4BGAsYHg/w180-h320/localhost_3000_%2528iPhone%2B5_SE%2529.png" width="180" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: left;"><br /></div><div><div><ul><li>Google Drive内の所定のフォルダからファイル一覧を表示。</li><li>ファイル名がクリックされるとその内容(テキストデータ)を表示。</li><li>テキストを入力・編集して保存ボタンを押し、ファイル名を入力するとGoogle Driveにテキストデータを保存。</li></ul></div><div><br /></div><div>ということを行っています。</div><div><br /></div></div><div><br /></div><div><br /></div><h2 style="text-align: left;">2つのContextプロバイダー</h2><div><br /></div><div>認証状態とアプリケーション固有の状態を分けて2つのContextプロバイダーで別々に共有するようにします。</div><div><br /></div></div></div></div><pre style="text-align: left;">import React from 'react';<br />import { FileList } from './FileList';<br />import { FileContent } from './FileContent';<br /><b><font color="#3367d6">import { AuthProvider } from './auth-state';<br />import { AppStateProvider } from './app-state';<br /></font></b>import { Header } from './Header';<br />import './App.css';</pre><pre style="text-align: left;"><br />function App() {<br /> return (<br /><b><font color="#3367d6"> <AuthProvider><br /> <AppStateProvider><br /></font></b> <div className="App"><br /> <Header /><br /> <FileList /><br /> <FileContent /><br /> </div><br /><b><font color="#3367d6"> </AppStateProvider><br /> </AuthProvider><br /></font></b> );<br />}<br />export default App;<br /></pre><div><div style="text-align: left;"><br /></div><div style="text-align: left;"><br /></div><div><br /></div><h2 style="text-align: left;">Google Drive APIの有効化とAPI Keyの作成</h2><div><br /></div><div>下のドキュメントにしたがってGoogle Drive APIを有効化し、API Keyを作成しておいてください。</div><div><br /></div><div><div><a href="https://developers.google.com/drive/api/v3/quickstart/js" target="_blank">Browser Quickstart | Google Drive API</a> </div><div>https://developers.google.com/drive/api/v3/quickstart/js</div></div><div><br /></div><div><br /></div><div><br /></div><h2 style="text-align: left;">Google DriveへのCRUD処理</h2><div><br /></div><div>次にGoogle Driveにアクセスする処理を「google-api.js」に記述します。</div><div><br /></div><div>google-api.js</div><div>---</div><script src="https://gist.github.com/mikehibm/bcd377fb3e86a5a048a9ea072966afea.js"></script><div>---</div><div><br /></div><div>前回作成した認証関係の関数に加えて、</div><div><br /></div></div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"><div><div style="text-align: left;"><table border="1" bordercolor="#888" cellspacing="0" style="border-collapse: collapse; border-color: rgb(136, 136, 136); border-width: 1px;"><tbody><tr><td style="min-width: 60px;"> getFiles</td><td style="min-width: 60px;"> ファイル一覧取得 </td></tr><tr><td style="min-width: 60px;"> getFileContent <span> </span></td><td style="min-width: 60px;"> ファイル内容取得</td></tr><tr><td> uploadFile</td><td> ファイル保存</td></tr><tr><td> deleteFile</td><td> ファイル削除</td></tr></tbody></table></div></div></blockquote><div><div><br /></div><div>などの関数をエクスポートしています。</div><div><br /></div><div><br /></div><div><br /></div><div>アップロード処理がなかなか動かず苦労しましたが、最終的には下のサイトの情報にしたがってMultipart形式でPOST(更新のときはPATCH)リクエストを送ることで解決しました。</div><div><br /></div><div><div><a href="https://tanaikech.github.io/2018/08/13/upload-files-to-google-drive-using-javascript/" target="_blank">Upload Files to Google Drive using Javascript · tanaike</a> </div><div><br /></div></div><div><br /></div><div>またファイルの内容を取得する方法も最初は分からず試行錯誤しました。こちらは下のURLにGETリクエストを送ることでダウンロード出来ました。</div><div><br /></div><div><div><font color="#3367d6" size="2"><b>https://www.googleapis.com/drive/v3/files/${fileId}?alt=media&source=downloadUrl</b></font></div></div><div><br /></div><div>もちろんリクエストヘッダーにはGoogle認証で得られたアクセストークンを付加する必要があります。</div><div><br /></div><div><br /></div><div>ファイルの保存先フォルダは現状では APP_FOLDER という定数で定義していますが、これはユーザーが任意のフォルダ名を指定出来るようにした方が便利ですね。</div><div><br /></div><div><br /></div><div>以上、今回のサンプルを作成してみて、バックエンドサーバ無しでもブラウザ上のJavaScriptだけでかなり柔軟にGoogle Driveへのデータ保存が出来るということが分かりました。</div><div><br /></div><div><br /></div><div>単純なテキストではなく例えばJSONを保存すれば、ある意味簡易的なデータベースとしても使えるかも知れませんね。何よりアプリの開発者側で保存先のデータベースやストレージを用意する必要が無いのが魅力的です。</div><div><br /></div><div><br /></div><div><br /></div><div>今回作成したアプリケーションのソースコードは<a href="https://github.com/mikehibm/googledrive-notebook" target="_blank">こちら</a>にあります。</div><div><a href="https://github.com/mikehibm/googledrive-notebook" target="_blank">https://github.com/mikehibm/googledrive-notebook</a></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div> </div></div>Makotohttp://www.blogger.com/profile/09442687702666423250noreply@blogger.comtag:blogger.com,1999:blog-37629199.post-73532335098158149652020-06-14T09:23:00.018+09:002020-08-04T10:51:10.339+09:00ReactのSPAアプリケーションでContextとHooksを使ってGoogle認証を実装する<div><div>ReactのSPAアプリケーションにおいて、ContextとHooksを使うことでGoogle認証の処理をラップして利用するというサンプルを作成しました。</div><div><br /></div></div><div><br /></div><h3 style="text-align: left;">Google Cloud Consoleでアプリケーションを登録する</h3><div><br /></div><div><a href="https://console.cloud.google.com/">https://console.cloud.google.com/</a></div><div><br /></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhqSCnl9sA7S-Yk9jtr28zfuL_q3ibouYqw1MipggLJ8Ddmuyf-aelhApF0JFV1HZT__WLrtNM1RmR2NhFYHq9k1egzSO0ZVII9bK8M0og_5MKhcbyr5GwORAqCVKyRa1SvCf3J/s1368/Screen+Shot+2020-06-12+at+11.29.43+AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1176" data-original-width="1368" height="344" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhqSCnl9sA7S-Yk9jtr28zfuL_q3ibouYqw1MipggLJ8Ddmuyf-aelhApF0JFV1HZT__WLrtNM1RmR2NhFYHq9k1egzSO0ZVII9bK8M0og_5MKhcbyr5GwORAqCVKyRa1SvCf3J/w400-h344/Screen+Shot+2020-06-12+at+11.29.43+AM.png" width="400" /></a></div><div><br /></div><h3 style="text-align: left;">OAuth consent screenを設定する </h3><div class="separator" style="clear: both; text-align: center;"><br /><div style="text-align: left;"><br /></div></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVrl8K3HoLFjcTGs2ByVQqVWOgHoRbLru3fIH4FBzPSuWnHWQRuQKwAuJAVZXEZhiCZQ25TkDYax6LQ9Ec_Y5ww-uqj_nCBvHMEFqkU3u_K4gfKCk06Y9R-EvQApgDdCxUi3M4/s1630/Screen+Shot+2020-06-12+at+11.42.39+AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1056" data-original-width="1630" height="259" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVrl8K3HoLFjcTGs2ByVQqVWOgHoRbLru3fIH4FBzPSuWnHWQRuQKwAuJAVZXEZhiCZQ25TkDYax6LQ9Ec_Y5ww-uqj_nCBvHMEFqkU3u_K4gfKCk06Y9R-EvQApgDdCxUi3M4/w400-h259/Screen+Shot+2020-06-12+at+11.42.39+AM.png" width="400" /></a></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-7asi2Je1FwM/XuP42-x8zHI/AAAAAAAAa4c/huKtnhCHgOU3Kuc7T9o7ZA-I2f7h2k_NQCK4BGAsYHg/s1694/Screen%2BShot%2B2020-06-12%2Bat%2B11.50.50%2BAM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1020" data-original-width="1694" height="241" src="https://1.bp.blogspot.com/-7asi2Je1FwM/XuP42-x8zHI/AAAAAAAAa4c/huKtnhCHgOU3Kuc7T9o7ZA-I2f7h2k_NQCK4BGAsYHg/w400-h241/Screen%2BShot%2B2020-06-12%2Bat%2B11.50.50%2BAM.png" width="400" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div><br /></div><div><br /></div><h3 style="text-align: left;">OAuth client IDを作成する</h3><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-BW0cKxkkWHk/XuP4lTOAGOI/AAAAAAAAa4E/rk6JAQRuRQYcxYMj_iKO-SFUQKhD155agCK4BGAsYHg/s1974/Screen%2BShot%2B2020-06-12%2Bat%2B11.41.43%2BAM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1066" data-original-width="1974" height="216" src="https://1.bp.blogspot.com/-BW0cKxkkWHk/XuP4lTOAGOI/AAAAAAAAa4E/rk6JAQRuRQYcxYMj_iKO-SFUQKhD155agCK4BGAsYHg/w400-h216/Screen%2BShot%2B2020-06-12%2Bat%2B11.41.43%2BAM.png" width="400" /></a></div><div><br /></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjunlDFwlk6SZ8k-G-pjB4_tCojJ1YDSrskDnhmPeuB7AweQHe8A2Sc1grsz9oA7cYdQzBbOU5nZ6Ws1WHqd3dtw0iP13ZghFtv7aUb55oihYdfuqzJQGpRLsQ1gPxf_7HfkXDE/s1418/Screen+Shot+2020-06-12+at+11.53.54+AM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1418" data-original-width="1232" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjunlDFwlk6SZ8k-G-pjB4_tCojJ1YDSrskDnhmPeuB7AweQHe8A2Sc1grsz9oA7cYdQzBbOU5nZ6Ws1WHqd3dtw0iP13ZghFtv7aUb55oihYdfuqzJQGpRLsQ1gPxf_7HfkXDE/w348-h400/Screen+Shot+2020-06-12+at+11.53.54+AM.png" width="348" /></a></div><div><br /></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-X9ddhLGLyEI/XuP7qRa5BmI/AAAAAAAAa5Y/Wi2-WuqeM2sY0j-1gbhaDsoA4ENiIMKTACK4BGAsYHg/s2282/Screen%2BShot%2B2020-06-12%2Bat%2B12.01.42%2BPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1106" data-original-width="2282" height="194" src="https://1.bp.blogspot.com/-X9ddhLGLyEI/XuP7qRa5BmI/AAAAAAAAa5Y/Wi2-WuqeM2sY0j-1gbhaDsoA4ENiIMKTACK4BGAsYHg/w400-h194/Screen%2BShot%2B2020-06-12%2Bat%2B12.01.42%2BPM.png" width="400" /></a></div><div><br /></div><div><br /></div><h3 style="text-align: left;">Create React AppでReactアプリケーションを作成する</h3><div><br /></div><div>npx create-react-app project-name</div><div>cd project-name</div><div><br /></div><div><br /></div><h3 style="text-align: left;">public/index.html を編集する</h3><div><br /></div><div>WebアプリケーションでGoogle認証を利用するための最もシンプルなサンプルコードがこちらにあります。</div><div><br /></div><div>Google Sign-In for Websites | Google Developers
</div><div><a href="https://developers.google.com/identity/sign-in/web" target="_blank">https://developers.google.com/identity/sign-in/web</a></div><div><br /></div><div style="text-align: left;"><pre><html lang="en"><br /> <head><br /> <meta name="google-signin-scope" content="profile email"><br /> <meta name="google-signin-client_id" content="YOUR_CLIENT_ID.apps.googleusercontent.com"><br /><b><font color="#3367d6"> <script src="https://apis.google.com/js/platform.js" </font><font color="#d52c1f">async defer</font><font color="#3367d6">></script><br /></font></b> </head><br /> <body><br /> <div class="g-signin2" data-onsuccess="onSignIn" data-theme="dark"></div><br /> <script><br /> function onSignIn(googleUser) {<br /> // Useful data for your client-side scripts:<br /> var profile = googleUser.getBasicProfile();<br /> console.log("ID: " + profile.getId()); // Don't send this directly to your server!<br /> console.log('Full Name: ' + profile.getName());<br /> console.log('Given Name: ' + profile.getGivenName());<br /> console.log('Family Name: ' + profile.getFamilyName());<br /> console.log("Image URL: " + profile.getImageUrl());<br /> console.log("Email: " + profile.getEmail());<br /> // The ID token you need to pass to your backend:<br /> var id_token = googleUser.getAuthResponse().id_token;<br /> console.log("ID Token: " + id_token);<br /> }<br /> </script><br /> </body><br /></html></pre></div><div style="text-align: left;"><br /></div><div>これを参考に public/index.htmlを編集します。</div></div><div><br /></div><div><div>上のサンプルコードの中でReactアプリで必要になるのはGoogleのスクリプトを読み込んでいる行だけなので、その部分をコピーしてheadタグ内に挿入します。</div><div><br /></div><div><pre style="text-align: left;"> <font color="#3367d6"><b><script src="https://apis.google.com/js/platform.js"></script></b></font></pre></div><div><br /></div><div>Googleのサンプルコードでは「async defer」という属性が付いていますが、Create React Appの場合これがあるとスクリプトが実行されるタイミングの関係で上手く動かないので削除しています。</div><div><br /></div><div><br /></div><div><br /></div><h3 style="text-align: left;">.env.localファイルを作成する</h3><div><br /></div><div>次にプロジェクトのルートフォルダに「.env.local」 ファイルを作成して値を設定しておきましょう。</div><div><br /></div></div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px;"><div><pre style="text-align: left;"><font color="#3367d6"><b>REACT_APP_CLIENTID</b></font>=41702000-xxxxxxxxxxxxx.apps.googleusercontent.com</pre></div></blockquote><div><div><br /></div><div>この環境変数の名前は、Create React Appの規約上「REACT_APP_」から始まる必要があるので注意してください。</div><div><br /></div><div>ClientIdを環境変数に持たせておくことによって、本番用、ステージング用など、ビルド環境に応じて柔軟に変更することが出来るようになります。</div><div><br /></div><div><br /></div><h3 style="text-align: left;">src/google-auth.jsファイルを作成する</h3><div><br /></div><div>Google APIを実際に呼び出す際に使う関数をこのファイルにまとめておきます。</div><div><br /></div><div>init(), signIn(), signOut()の3つの関数をエクスポートしています。<br />---<script src="https://gist.github.com/mikehibm/3aa1df5753beac5bee4ce1f39e01b4fc.js"></script>---</div><div><br /></div><div><br /></div><h3 style="text-align: left;">src/auth-state.jsファイルを作成する</h3><div><br /></div><div>上のgoogle-auth.jsに定義した関数をReactアプリ内で使うためのContextプロバイダーとフックを提供するファイルになります。</div><div><br /></div><div>内部的にはuseReducerを使って認証状態を管理していますが、それはファイル内に隠蔽して外部へはAuthProviderコンポーネントとuseAuthStateフックをエクスポートしています。</div><div><br /></div><div>useAuthStateフックからの戻り値は、認証状態を表す state オブジェクトとサインイン、サインアウト処理を呼び出すための関数を提供する actions オブジェクトの2要素配列となっています。</div><div><br /></div><div>---</div><script src="https://gist.github.com/mikehibm/83745c7efe8a30816179585440f70cae.js"></script><div>---</div><div><br /></div><div><br /></div><div>ここまで準備が出来れば、あとは</div><div><br /></div><div>1. AuthProviderコンポーネントでアプリをラップする。</div><div>2. 任意のコンポーネント内でuseAuthStateフックを使って認証機能にアクセスする。</div><div><br /></div><div>という形になります。</div><div><br /></div></div><div><br /></div><div>App.js</div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><div><pre style="text-align: left;">function App() {<br /> return (<br /> <font color="#d52c1f"><b><AuthProvider></b></font><br /> <div className="App"><br /> <header className="App-header"><br /> <h1>Google Auth React Examle</h1><br /> <SignInOutButton /><br /> </header><br /> <div className="App-main"><br /> <UserInfo /><br /> </div><br /> </div><br /> <font color="#d52c1f"><b></AuthProvider></b></font><br /> );<br />}</pre></div></blockquote><div><div><br /></div><div><br /></div><div>SignInOutButton.js</div></div><div> </div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><div><font face="monospace"><span style="white-space: pre;">import React from 'react';
import { useAuthState } from './auth-state';</span></font> <pre>export function SignInOutButton() {<br /> const [state, actions] = <font color="#d52c1f"><b>useAuthState()</b></font>;<br /> const { isSignedIn } = state;<br /> const { signIn, signOut } = actions;<br /> if (isSignedIn === undefined) {<br /> return null;<br /> }<br /> return (<br /> <div><br /> {isSignedIn ? (<br /> <button onClick={signOut}>Sign Out</button><br /> ) : (<br /> <button onClick={signIn}>Sign In</button><br /> )}<br /> </div><br /> );<br />}</pre></div></blockquote><div><div style="text-align: left;"><br /></div><div style="text-align: left;"><br /></div><div><br /></div><div><br /></div><div>今回作成したアプリケーションのソースコードは<a href="https://github.com/mikehibm/googleauth-react-202006" target="_blank">こちら</a>にあります。</div><div><a href="https://github.com/mikehibm/googleauth-react-202006" target="_blank">https://github.com/mikehibm/googleauth-react-202006</a></div><div><br /></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-2d1PCDvNrlk/XuWCSn6xO7I/AAAAAAAAa7Y/Be8YR6zvFBIAzm3gOhv6t1Emof0agZENQCK4BGAsYHg/s600/screen2.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="457" data-original-width="600" height="305" src="https://1.bp.blogspot.com/-2d1PCDvNrlk/XuWCSn6xO7I/AAAAAAAAa7Y/Be8YR6zvFBIAzm3gOhv6t1Emof0agZENQCK4BGAsYHg/w400-h305/screen2.png" width="400" /></a></div><div><br /></div><div><br /></div><div><br /></div><div>以上、ReactのSPAアプリケーションにおいて、ContextとHooksを使ってGoogle認証の処理をラップするサンプルでした。</div><div><br /></div><div><br /></div><div><br /></div><div><a href="https://blog.makotoishida.com/2020/06/reactgoogle-drive-apigoogle-drive.html" target="_blank">次回</a>は、認証した後さらにGoogle Drive APIを使ってアプリケーションのデータを(バックエンドサーバー無しで)ブラウザから直接Google Driveに保存するということに挑戦したいと思います。</div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div> </div></div>Makotohttp://www.blogger.com/profile/09442687702666423250noreply@blogger.comtag:blogger.com,1999:blog-37629199.post-79192381066120531852020-05-23T13:36:00.007+09:002020-06-14T09:37:27.752+09:00Macの音声読み上げ機能を言語別にキー一発で呼び出す方法<h2 style="text-align: left;">読み上げ機能は目に優しい</h2><div><br /></div><div>最近、ブラウザでニュースを読む時にMacの音声読み上げ機能を使うと、「目を閉じていてもニュースが頭に入ってくる」ことに気付きました。</div><div><br /></div><div><br /></div><div>これは慣れるとかなり便利。なんと言っても目の疲れが大幅に軽減できることが最大の利点です。</div><div><br /></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDfMhC4k6hjZCmtYJBzccpkI0U1fwyFLa2x8v2ODKfkMBdemHPUhE50BCUZ4GFIxETOgNvM_YKO-0UItmo0CnHeXCrUpmzqO-GgPsMAxiip589VBF1jKuA1ARhntvJyNmxgVNb/" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1128" data-original-width="1894" height="239" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDfMhC4k6hjZCmtYJBzccpkI0U1fwyFLa2x8v2ODKfkMBdemHPUhE50BCUZ4GFIxETOgNvM_YKO-0UItmo0CnHeXCrUpmzqO-GgPsMAxiip589VBF1jKuA1ARhntvJyNmxgVNb/w400-h239/Screen+Shot+2020-05-22+at+5.47.39+PM.png" width="400" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-t_9IYxH4_HE/XsieG8UHylI/AAAAAAAAaA8/OqJZpJgfX5oGY0k4W2N2MGUuXwIxp_tsQCK4BGAsYHg/Screen%2BShot%2B2020-05-22%2Bat%2B5.50.17%2BPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1152" data-original-width="1840" height="250" src="https://1.bp.blogspot.com/-t_9IYxH4_HE/XsieG8UHylI/AAAAAAAAaA8/OqJZpJgfX5oGY0k4W2N2MGUuXwIxp_tsQCK4BGAsYHg/w400-h250/Screen%2BShot%2B2020-05-22%2Bat%2B5.50.17%2BPM.png" width="400" /></a></div><div><br /></div><div><br /></div><div><br /></div><div>もちろんMacの標準状態でも、ブラウザで文章を選択して「Edit」メニュー → 「Start Speaking」を選べば読み上げ機能を使うことは可能です。</div><div><br /></div><div>アクセシビリティの設定で任意のショートカットキーを設定することも出来ます。</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-yPKnrG55_Cs/XsifsPCwGAI/AAAAAAAAaBY/xmMqCkOWYeUkxEkEfNrXnfH7eWAvRPO1ACK4BGAsYHg/Screen%2BShot%2B2020-05-22%2Bat%2B5.58.06%2BPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="872" data-original-width="1324" height="264" src="https://1.bp.blogspot.com/-yPKnrG55_Cs/XsifsPCwGAI/AAAAAAAAaBY/xmMqCkOWYeUkxEkEfNrXnfH7eWAvRPO1ACK4BGAsYHg/w400-h264/Screen%2BShot%2B2020-05-22%2Bat%2B5.58.06%2BPM.png" width="400" /></a></div><div><br /></div><div><br /></div><div>もし一つの言語しか読み上げさせる必要がないのであれば、これで大丈夫です。</div><div><br /></div><div><br /></div><div>ただ、複数の言語を使い分けたい場合は、これだと読み上げに使用する言語(音声)をその都度選ぶことが出来ません。</div><div><br /></div><div>日本語の音声を設定している状態で英語の文章を読み上げさせても、カナカナ読みの変な発音でしか読まれません。逆に英語の音声が選択されている状態で日本語を読み上げさせようとしても、上手く発音してくれません。</div><div><br /></div><div>つまり、<b>選択されている文章の言語によって読み上げに使う音声を切り替える</b>必要があるわけです。</div><div><br /></div><div><br /></div><div>これが出来るように今回設定した方法を、以下にメモしておきます。</div><div><br /></div><div><br /></div><h2 style="text-align: left;">1. 言語別にAutomatorでサービスを作成</h2><div><br /></div><div>Automatorを起動して、新しい Quick Action を作成します。</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-E5zmFQpttrc/Xsij_8PjUfI/AAAAAAAAaCc/jKs9SIPA2ykKVMmXUg1rIjTAywSTlGfAgCK4BGAsYHg/Screen%2BShot%2B2020-05-22%2Bat%2B6.16.58%2BPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1312" data-original-width="2246" height="234" src="https://1.bp.blogspot.com/-E5zmFQpttrc/Xsij_8PjUfI/AAAAAAAAaCc/jKs9SIPA2ykKVMmXUg1rIjTAywSTlGfAgCK4BGAsYHg/w400-h234/Screen%2BShot%2B2020-05-22%2Bat%2B6.16.58%2BPM.png" width="400" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div><br /></div><div><br /></div><div>Actionsから System → Speak Text を選んで、ドラッグアンドドロップで追加します。</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-rxEr64aT7mA/Xsikg3HTmkI/AAAAAAAAaCs/0o2SedGkshoKAR4yL4MgqiOEe9BHMyz_ACK4BGAsYHg/Screen%2BShot%2B2020-05-22%2Bat%2B6.19.10%2BPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="926" data-original-width="2018" height="184" src="https://1.bp.blogspot.com/-rxEr64aT7mA/Xsikg3HTmkI/AAAAAAAAaCs/0o2SedGkshoKAR4yL4MgqiOEe9BHMyz_ACK4BGAsYHg/w400-h184/Screen%2BShot%2B2020-05-22%2Bat%2B6.19.10%2BPM.png" width="400" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div><br /></div><div><br /></div><div>読み上げに使う言語に対応した音声を選んで、後から分かる名前を付けて保存します。</div><div><br /></div><div><br /></div><div>これを、使いたい言語の数だけ繰り返します。</div><div><br /></div><div><br /></div><div><br /></div><h2 style="text-align: left;">2. キーボードショートカットを設定</h2><div><br /></div><div>システム設定から「Keyboard」を選ぶと、「Services」のカテゴリ内に先ほどAutomatorで作ったQuick Actionが表示されます。</div><div><br /></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-oRTjgBX_TN0/Xsils2Ek-yI/AAAAAAAAaDM/tYaHH-rx4eUG-JxufRepyaYnkrsDTUdRgCK4BGAsYHg/Screen%2BShot%2B2020-05-22%2Bat%2B6.22.51%2BPM.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1066" data-original-width="1326" height="321" src="https://1.bp.blogspot.com/-oRTjgBX_TN0/Xsils2Ek-yI/AAAAAAAAaDM/tYaHH-rx4eUG-JxufRepyaYnkrsDTUdRgCK4BGAsYHg/w400-h321/Screen%2BShot%2B2020-05-22%2Bat%2B6.22.51%2BPM.png" width="400" /></a></div><div><br /></div><div><br /></div><div>それぞれの言語に対応したQuick ActionのチェックをONにして、好きなショートカットキーを設定すれば完了です。</div><div><br /></div><div>私の場合は、</div><div><br /></div><div>Ctrl + Cmd + w → 日本語読み上げ</div><div>Ctrl + Cmd + e → 英語読み上げ</div><div><br /></div><div>としています。</div><div><br /></div><div>なるべく片手ですぐ押さえられる組み合わせが良いと思います。</div><div><br /></div><div><br /></div><div><div>ブラウザを開いて何らかの文章を選択状態にすると、Servicesメニューに上で作成したQuick Actionが追加されているのが分かります。</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://1.bp.blogspot.com/-yn8uAO2IW3I/XslaplhR__I/AAAAAAAAaEY/s_QNuYYK2dYQVaUuoN42FU5RPp36SRhNwCK4BGAsYHg/Screen%2BShot%2B2020-05-22.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="260" data-original-width="373" height="279" src="https://1.bp.blogspot.com/-yn8uAO2IW3I/XslaplhR__I/AAAAAAAAaEY/s_QNuYYK2dYQVaUuoN42FU5RPp36SRhNwCK4BGAsYHg/w400-h279/Screen%2BShot%2B2020-05-22.png" width="400" /></a></div><div class="separator" style="clear: both; text-align: center;"><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div></div><div>この設定で、Google Chrome、Safari, Firefox のいずれのブラウザでも文章を選択してキー一発で言語別に読み上げ機能を使うことが出来るようになりました!</div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div> </div>Makotohttp://www.blogger.com/profile/09442687702666423250noreply@blogger.comtag:blogger.com,1999:blog-37629199.post-48867520153901916962020-05-07T16:18:00.002+09:002020-05-07T16:18:57.222+09:00Firebase Hostingにデプロイした静的サイトにFirebase Analyticsを追加する(Next.js)まずは、Firebaseの管理画面から、該当のプロジェクトでAnalyticsを有効化します。<br><br><br><blockquote><a href="https://firebase.google.com/docs/analytics/get-started?platform=web" target="_blank">Google アナリティクスを使ってみる</a></blockquote><div><br></div><div><br class="Apple-interchange-newline"><img border="0" data-original-height="1488" data-original-width="2088" height="285" src="https://1.bp.blogspot.com/-o_WfNrMU9q8/XrOmkrunrnI/AAAAAAAAZec/lSIiXQmgsCoAX6NhLr58fYWadxitVpR-QCK4BGAsYHg/w400-h285/Screen%2BShot%2B2020-05-06%2Bat%2B8.02.13%2BPM.png" style="color: #0000ee; text-align: center;" width="400"><br><br><br><br>ここではNext.jsからエクスポートした静的サイトを例にします。</div><div><br></div><div>pages/index.js など、全てのページで共通に含まれる部分で、下のようにHeadコンポーネントを使ってscriptタグを埋め込みます。</div><div><br></div><div>要は下の赤字部分の3行のscriptタグがHTMLのhead部分に含まれるようにすれば良いということですね。</div><div><br></div><div><pre style="text-align: left;">export default function Home({ allPostsData }) {<br> return (<br> <Layout home><br> <Head><br> <title>{siteTitle}</title><br><font color="#d52c1f"><b> <script src="/__/firebase/7.14.2/firebase-app.js"></script><br> <script src="/__/firebase/7.14.2/firebase-analytics.js"></script><br> <script src="/__/firebase/init.js"></script><br></b></font> </Head><br> <section className={utilStyles.headingMd}><br> <p>[Your Self Introduction]</p><br> </section><br> <section className={`${utilStyles.headingMd} ${utilStyles.padding1px}`}><br> <h2 className={utilStyles.headingLg}>Blog</h2><br> <ul className={utilStyles.list}><br> {allPostsData.map(({ id, date, title }) => (<br> <li className={utilStyles.listItem} key={id}><br> <Link href="/posts/[id]" as={`/posts/${id}`}><br> <a>{title}</a><br> </Link><br> </li><br> ))}<br> </ul><br> </section><br> </Layout><br> );<br>}</pre><div style="text-align: left;"><br></div><br>次に、下のドキュメントの通りにイベントを送信する必要があります。<br><br><blockquote><a href="https://firebase.google.com/docs/analytics/events?platform=web" target="_blank">イベントをロギングする</a></blockquote><div><br></div><div>このために、「send-ga-event.js」 と 「use-send-screenview.js」という2つのファイルを作成しました。</div><div><br></div><div><br></div><div><b>send-ga-event.js</b></div><div><br></div><div style="text-align: left;"><pre>export function sendGAEvent(eventName, params) {<br> const firebase = window && window.firebase;<br> if (!firebase) return;<br> try {<br> <b><font color="#b51200">firebase.analytics().logEvent(eventName, params);</font></b><br> } catch (error) {<br> console.error(error);<br> }<br>}</pre></div><div style="text-align: left;"><br></div><div style="text-align: left;"><br></div><div style="text-align: left;"><b>use-send-screenview.js</b></div><div style="text-align: left;"><br></div><div style="text-align: left;"><pre style="text-align: left;">import { useEffect } from 'react';<br>import { sendGAEvent } from './send-ga-event';</pre><pre style="text-align: left;">export function <b><font color="#3367d6">useSendScreenView</font></b>(screenName) {<br> return useEffect(() => {<br> const params = { screen_name: screenName };<br><b> <font color="#b51200">sendGAEvent('screen_view', params);</font></b><br> }, []);<br>}</pre><div style="text-align: left;"><br></div><div>上で定義したuseSendScreenView()という名前のフックを pages/index.jsから呼び出すことでAnalyticsにイベントを送信します。</div><div><br></div><div><br></div><div><b>pages/index.js</b></div><div><br></div><div><pre style="text-align: left;">import { useSendScreenView } from '../utils/use-send-screenview';</pre><pre style="text-align: left;">export default function Home({ allPostsData }) {</pre><pre style="text-align: left;"> <b><font color="#b51200">useSendScreenView('home');</font></b></pre><pre style="text-align: left;"> return (<br> <Layout home><br> <Head><br> <title>{siteTitle}</title><br> {process.env.NODE_ENV === 'production' && (<br> <><br> <script src="/__/firebase/7.14.2/firebase-app.js"></script><br> <script src="/__/firebase/7.14.2/firebase-analytics.js"></script><br> <script src="/__/firebase/init.js"></script><br> </><br> )}<br> </Head><br> <section className={utilStyles.headingMd}><br> <p>[Your Self Introduction]</p></pre><pre style="text-align: left;"><br>(以下省略...)</pre></div><div><br></div><div><br></div><div>これらの変更を行ったあと、</div></div><div style="text-align: left;"><b><br></b></div><div style="text-align: left;"><b>npm run build && npm run export</b></div><div style="text-align: left;"><b>firebase deploy </b></div><div style="text-align: left;"><br></div><div style="text-align: left;">でデプロイしてサイトを更新します。</div><div style="text-align: left;"><br></div><div style="text-align: left;">更新後のページを開くと、数秒でAnalyticsの画面にイベントが表示されるのが確認出来ます。</div><div style="text-align: left;"><br></div><br><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGSQMQNnbh95PbgfLdJnIxeZnlrGvrbmdajBcDbF4L5kIaRGIY_bZ8-lMztQpgIzI_-EOssM1oT9qTVvKmcyJD-XNNRXbp3C0AmiQ21IabQh3dNhBpLYPXrmA2TWL91_gUQkrP/" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1438" data-original-width="2186" height="422" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhGSQMQNnbh95PbgfLdJnIxeZnlrGvrbmdajBcDbF4L5kIaRGIY_bZ8-lMztQpgIzI_-EOssM1oT9qTVvKmcyJD-XNNRXbp3C0AmiQ21IabQh3dNhBpLYPXrmA2TWL91_gUQkrP/w640-h422/Screen+Shot+2020-05-06+at+9.01.23+PM.png" width="640"></a></div><div class="separator" style="clear: both; text-align: center;"><br></div><div class="separator" style="clear: both; text-align: center;"><br></div><div class="separator" style="clear: both; text-align: left;"><br></div><div class="separator" style="clear: both; text-align: left;"><br></div><div class="separator" style="clear: both; text-align: left;"><br></div><div class="separator" style="clear: both; text-align: left;"><br></div><div class="separator" style="clear: both; text-align: left;"> </div></div><a href="https://blog.makotoishida.com/2020/01/nextjsfirebase-analytics.html#more"></a>Makotohttp://www.blogger.com/profile/09442687702666423250noreply@blogger.comtag:blogger.com,1999:blog-37629199.post-54579448657701821322020-05-07T12:54:00.006+09:002020-05-07T16:26:09.545+09:00Next.jsで静的サイトを出力してFirebase Hostingでホスティングするまずは公式のドキュメントにしたがってNext.jsのアプリケーションを作成しましょう。<br /><br />Create a Next.js App | Learn Next.js <div><div> <a href="https://nextjs.org/learn/basics/create-nextjs-app">https://nextjs.org/learn/basics/create-nextjs-app</a></div><div><br /></div><div><br /></div><div>package.jsonファイルのscriptsセクションに下記を追加しておきます。</div><div><br /></div><div><pre style="text-align: left;"> "scripts": {<br /> "dev": "next dev",<br /> "build": "next build",<br /> "start": "next start",<br /><font color="#3367d6"> <b>"export": "next export"</b></font><br /> },</pre></div><div><br /></div><div><br /></div><div><b>npm run build && npm run export</b> を実行して outフォルダに結果が出力されるのを確認します。</div><div><br /></div><div><br /></div><div><br /></div><div>次にFirebase CLIを初期化します。</div><div><br /></div><div><a href="https://firebase.google.com/docs/hosting/quickstart" target="_blank"><blockquote>Firebase Hosting を使ってみる</blockquote></a> </div><div><br /></div><div><b>firebase login</b></div><div><br /></div><div><b>firebase init</b></div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2hL04Zt281vXmFhSjGk-xIepMLMsTKAvGjTX-St2JqyZSE1B162i0XBPY_PBhGUzHudFzc4kH2ofoEclI75ExkgRy9u0pL_-LNMrwuLhi9qpJyvkHS3ce0hE-M33EE44_GeTC/" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="672" data-original-width="1842" height="146" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh2hL04Zt281vXmFhSjGk-xIepMLMsTKAvGjTX-St2JqyZSE1B162i0XBPY_PBhGUzHudFzc4kH2ofoEclI75ExkgRy9u0pL_-LNMrwuLhi9qpJyvkHS3ce0hE-M33EE44_GeTC/w400-h146/Screen+Shot+2020-05-06+at+5.25.15+PM.png" width="400" /></a></div><div><br /></div><div><br /></div><div>カーソルキーで「Hosting」を選択してスペースキーを押し、Enterで確定です。</div><div><br /></div><div>Firebaseの管理画面でまだ「プロジェクト」を作成していない場合は、プロジェクトIDと名称を入力して新規作成します。既に使われているIDを入れてしまうとエラーになるので、ユニークなIDを入力しましょう。</div><div><br /></div><div>アップロード対象のフォルダを聞かれるので、デフォルトの「public」ではなく「<b><font color="#b51200">out</font></b>」を指定します。</div><div><br /></div><div><br /></div><div><b>firebase deploy</b> を実行して、「Deploy complete!」と表示されれば完了です!</div><div><br /></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgIP24TSgmn-WL6ANXiHmFDxsotnRNP1mY0_Pf8WSwggJhCU6V6PN3YauiQTu41wiasUautny9TC7FyPSYLtsO6Z8PzyzaGco7-Abdr-sCS4TcCZ4nAj5gGR6CHSVxx3tO5yud/" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="486" data-original-width="1212" height="160" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhgIP24TSgmn-WL6ANXiHmFDxsotnRNP1mY0_Pf8WSwggJhCU6V6PN3YauiQTu41wiasUautny9TC7FyPSYLtsO6Z8PzyzaGco7-Abdr-sCS4TcCZ4nAj5gGR6CHSVxx3tO5yud/w400-h160/Screen+Shot+2020-05-06+at+5.47.55+PM.png" width="400" /></a></div><div><br /></div><div><div>慣れれば3分もかからずに出来てしまいますね。</div><div><br /></div><div><br /></div></div><div>バックエンド処理にCloud Functionsを使ったりするような場合も、下のドキュメントの通り設定すれば決して難しくはありません。</div><div><br /></div><div><a href="https://firebase.google.com/docs/hosting/full-config#direct_requests_to_a_function" target="_blank"><blockquote>ホスティング動作を構成する</blockquote></a><div><a href="https://firebase.google.com/docs/hosting/serverless-overview" target="_blank"><blockquote>Firebase Hosting を使用した動的コンテンツの配信とマイクロサービスのホスティング</blockquote></a><br /></div><div> </div></div><div>本当に便利な世の中になったものです。^^)</div><div><br /></div><div><br /></div><div>あ、特にFirebase Hostingでなくても良いのであれば、もちろんNext.jsの開発元であるVercel(旧Zeit)社のサービスを使うのが一番簡単でオススメです。</div><div><br /></div><div><a href="https://vercel.com/" target="_blank">https://vercel.com/</a></div><div><br /></div><div><br /></div><div>こちらであれば、静的サイトでなくSSRありのサイトであっても、またバックエンドのAPIが必要な場合でも、何も考えずにデプロイしてしまえばあとはサービス側で上手くやってくれるみたいなので、こちらも素晴らしいと思います。</div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br />
<br /> </div></div>Makotohttp://www.blogger.com/profile/09442687702666423250noreply@blogger.comtag:blogger.com,1999:blog-37629199.post-45607557168864145622020-05-04T15:54:00.004+09:002020-05-07T12:15:45.775+09:00ReactでSVGファイルをコンポーネントとしてレンダリングする<a href="https://egghead.io/" target="_blank">egghead.io</a>のレッスンを観ていて良さそうなものがあったので、メモしておきます。<br />
<br />
<br />
Add SVGs as React Components with Create React App 2.0<br />
<a href="https://egghead.io/lessons/react-add-svgs-as-react-components-with-create-react-app-2-0" target="_blank">https://egghead.io/lessons/react-add-svgs-as-react-components-with-create-react-app-2-0</a><br />
<br />
Create React App のドキュメントにも記述があります。<br />
<a href="https://create-react-app.dev/docs/adding-images-fonts-and-files" target="_blank">https://create-react-app.dev/docs/adding-images-fonts-and-files</a><br />
<br />
<br />
Create React Appで作ったReactアプリケーションでは画像ファイルを下のようにインポートしてコンポーネント内で参照することが出来ます。<br />
<br />
<pre>import logo from './logo.png';
<pre>function Header() {<br /> return <img alt="Logo" src="{logo}" />;<br />}<br /></pre></pre>
<br />
もちろん画像の形式がSVGであっても同様です。<br />
<br />
<pre style="text-align: left;">import logo from './logo<b><span style="color: magenta;">.svg</span></b>';
<pre>function Header() {<br /> return <img alt="Logo" src="{logo}" />;<br />}</pre></pre>
<br />
ただ、SVG形式の場合はインポートの仕方を下のように変えるとReactコンポーネントとしても使えるようになるそうです。<br />
<br />
<pre style="text-align: left;">import <span style="color: magenta;"><b>{ ReactComponent as Logo }</b></span> from './logo.svg';
<pre>function App() {<br /> return (<br /> <div className="App"><br /> <header className="App-header"><br /> <<span style="color: magenta;"><b>Logo</b></span> className="App-logo" /><br /> </header><br /> </div>);<br />}</pre></pre>
<br />
<br />
このようにすると、HTMLとしてレンダリングされる際のタグが imgタグではなく、svgタグになります。<br />
<br />
svgタグになってうれしい点は、SVG内の個々の要素(図形)をCSSでコントロール出来るようになることです。<br />
<br />
<br />
例えば、線の色や太さを変えたり、アニメーションを付け加えたりすることが可能になります。<br />
<br />
<br />
下の例では、Reactロゴのpathエレメントの部分のみ点線にして、さらに点線のオフセットをアニメーションで動かしています。<br />
<br />
<pre>.App-logo path {
<pre> stroke: palegoldenrod;<br /> stroke-width: 10px;<br /> fill: none;<br /> stroke-dasharray: 35px 15px;<br /> animation: orbit 1s infinite linear;<br />}
@keyframes orbit {<br /> to {<br /> stroke-dashoffset: 50px;<br /> }<br />}</pre></pre>
<br />
実行するとこんな感じになります。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen='allowfullscreen' webkitallowfullscreen='webkitallowfullscreen' mozallowfullscreen='mozallowfullscreen' width='320' height='266' src='https://www.blogger.com/video.g?token=AD6v5dwv3IJbU6EX_RsWdVPBxvEvj4sC1FHutN8QBGiWnwlL5tk6iqIV0TkW4HlBI_vzgKPsZ3wuwgn_vhU' class='b-hbp-video b-uploaded' frameborder='0'></iframe></div>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />Makotohttp://www.blogger.com/profile/09442687702666423250noreply@blogger.comtag:blogger.com,1999:blog-37629199.post-23105254663918459012020-04-20T17:55:00.002+09:002020-04-22T05:08:54.295+09:00AWS AmplifyとDynamoDBでサーバーレスなREST APIを構築する<a href="https://blog.makotoishida.com/2020/04/aws-amplifyreact.html" target="_blank">前回</a>はユーザー認証機能を付けましたが、まだデータの永続化が出来ていないのでTodoアプリとしては未完成です。<br />
<br />
<br />
<br class="Apple-interchange-newline" />
1) <a href="https://blog.makotoishida.com/2020/04/nextjsaws-amplify-console.html" target="_blank">Next.jsでスタティック・エクスポートしたサイトをAWS Amplify Consoleでホスティングする</a><br />
<br />
2) <a href="https://blog.makotoishida.com/2020/04/aws-amplifyreact.html" target="_blank">AWS AmplifyでReactアプリにユーザー認証機能を追加する</a><br />
<br />
3) <a href="https://blog.makotoishida.com/2020/04/aws-amplifydynamodbrest-api.html" target="_blank">AWS AmplifyとDynamoDBでサーバーレスなREST APIを構成する</a><br />
<br />
<br />
<br />
<br />
今回はサーバーレスなREST APIを追加してデータをデータベースに保存出来るようにします。<br />
<br />
<b><i>amplify status</i></b> で現状を確認すると、下のようになっています。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-TVQzNNED65s/Xpu2V0uu3pI/AAAAAAAAZJE/SmTfkSarPKgpcpDGu68z-t0qeeun8wiYACLcBGAsYHQ/s1600/Screen%2BShot%2B2020-04-18%2Bat%2B4.23.00%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="274" data-original-width="1156" height="93" src="https://1.bp.blogspot.com/-TVQzNNED65s/Xpu2V0uu3pI/AAAAAAAAZJE/SmTfkSarPKgpcpDGu68z-t0qeeun8wiYACLcBGAsYHQ/s400/Screen%2BShot%2B2020-04-18%2Bat%2B4.23.00%2BPM.png" width="400" /></a></div>
<br />
<br />
下のコマンドでAPIカテゴリを追加します。<br />
<br />
<blockquote class="tr_bq">
<b><i>amplify add api </i></b></blockquote>
<br />
まず GraphQL か REST かを選ぶように言われます。<br />
<br />
Web上で見つかる情報にはGraphQLを使ったものが圧倒的に多いように思いますが、ここではあえてRESTを選びます。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-w9Y9TIhCiNg/Xpu45H3ageI/AAAAAAAAZJQ/jWm4IuWXcYkCwWKFVFi8FdgmEPWCv34QQCLcBGAsYHQ/s1600/Screen%2BShot%2B2020-04-18%2Bat%2B4.34.37%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="408" data-original-width="1600" height="101" src="https://1.bp.blogspot.com/-w9Y9TIhCiNg/Xpu45H3ageI/AAAAAAAAZJQ/jWm4IuWXcYkCwWKFVFi8FdgmEPWCv34QQCLcBGAsYHQ/s400/Screen%2BShot%2B2020-04-18%2Bat%2B4.34.37%2BPM.png" width="400" /></a></div>
<br />
<br />
DynamoDB上に作成するテーブル名まで入力すると、次に作成するカラム情報の入力になります。<br />
<br />
<a href="https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/bp-general-nosql-design.html" target="_blank">ドキュメント</a>をざっと読んだところ、DynamoDBでは基本的に1アプリケーションで使うテーブルは出来るだけ少ない方が望ましいらしく、「1テーブルで済ませられればそれが最も良い」との事です。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-_Irwx0Ffp7c/Xpu6EZVxIuI/AAAAAAAAZJY/03E4Y5VkQ64nm3sLtbCzJnbPtKGP3ugnQCLcBGAsYHQ/s1600/Screen%2BShot%2B2020-04-18%2Bat%2B4.40.06%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="153" data-original-width="1600" height="37" src="https://1.bp.blogspot.com/-_Irwx0Ffp7c/Xpu6EZVxIuI/AAAAAAAAZJY/03E4Y5VkQ64nm3sLtbCzJnbPtKGP3ugnQCLcBGAsYHQ/s400/Screen%2BShot%2B2020-04-18%2Bat%2B4.40.06%2BPM.png" width="400" /></a></div>
<br />
<br />
長年リレーショナルデータベースに馴染んだ身としてはなかなか目から鱗な考え方です。<br />
<br />
どうやらDynamoDBでのスキーマ設計の勘所は、「パーティションキー」と「ソートキー」および「グローバル・セカンダリインデックス」「ローカル・セカンダリインデックス」を上手く使うことにあるようです。<br />
<br />
<blockquote class="tr_bq">
<i><a href="https://hack-le.com/dynamodb-query/" target="_blank">DynamoDBのテーブル設計をするとき、自分に問いかけていること – 或る阿呆の記</a> (</i><i> <a href="https://hack-le.com/dynamodb-query/" target="_blank">https://hack-le.com/dynamodb-query/</a> )</i></blockquote>
<br />
この辺り、非常に奥が深そうで面白いのですがとりあえず今はシンプルなTodoリストアプリを作りたいだけなので、極力簡単な方法で行きたいと思います。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-fAnezJOjBVk/Xpu9JzV3r3I/AAAAAAAAZJk/ZGjvvgh8fu0AFMP7XFMzkhIx0pRk4qIbACLcBGAsYHQ/s1600/Screen%2BShot%2B2020-04-18%2Bat%2B4.53.15%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1214" data-original-width="1494" height="325" src="https://1.bp.blogspot.com/-fAnezJOjBVk/Xpu9JzV3r3I/AAAAAAAAZJk/ZGjvvgh8fu0AFMP7XFMzkhIx0pRk4qIbACLcBGAsYHQ/s400/Screen%2BShot%2B2020-04-18%2Bat%2B4.53.15%2BPM.png" width="400" /></a></div>
<br />
<br />
パーティションキーを「pk」、ソートキーを「sk」とし、3つ目のカラムはmap型で「data」としておきました。map型にはJSONを格納できるので、こうしておけば後からの仕様変更にもある程度柔軟に対応することが可能になるかと思います。<br />
<br />
パーティションキーとソートキーを何にするかというのは、アプリケーションの要件によって大きく変わる部分で、開発効率にも大きく関わってきます。<br />
<br />
<br />
今回は、pkカラムには認証されたユーザーのidに "user:" というプリフィックスを付加して格納し、skカラムにはTodoアイテムのid(ランダムに生成されたもの)の先頭に "todo:"というプリフィックスを付加して格納することにしました。<br />
<br />
こうすることでユーザーのidが分かればそのユーザーに属するタスクの一覧を簡単に取得することが出来るようになります。<br />
<br />
実際のクエリーとしては、<br />
- pkが "user:" + ユーザーid に一致する<br />
- skが "todo:" で始まる<br />
という条件で検索することになります。<br />
<br />
DynamoDBではパーティションキーは完全一致でしか検索出来ませんが、ソートキーは部分一致や範囲検索が可能なので、このようなクエリーが可能になります。<br />
<br />
また、ユーザーに属するデータでTodo項目以外のデータを保存したい場合には、skに付けるプリフィックスを "todo:" 以外のものにすれば対応出来ます。<br />
<br />
例えば、ユーザーごとのアプリ設定を保存したい場合は、pkは同じでskを "pref:" として保存すれば、簡単にそのレコードを一意に指定して読み出す事が出来ます。<br />
<br />
1アプリケーションで一つのテーブルしか使わない、というのはこのように2つのキー(およびセカンダリインデックス)を上手く使って複数の種類のデータを保存する、ということになるのかなと思います。<br />
<br />
<br />
カラム設定の入力が終わると、テーブルへのアクセス権の設定をするか聞かれるので、認証していないユーザーはAPIへのアクセスが出来ない様に設定しておきます。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPsW4Aw4wFektnH3jA5m1dgbZTzVE6DChdDISbhHDMPE6-gko6Nfi8qZ0gQaCPVvW_w8fxbvday3Wgn4dGkh4XXAfefUlSO9hkzN-RwsAeOFba_bGR7vpBqz54C2cBJoGKFtnB/s1600/Screen+Shot+2020-04-18+at+4.58.56+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="655" data-original-width="1600" height="163" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiPsW4Aw4wFektnH3jA5m1dgbZTzVE6DChdDISbhHDMPE6-gko6Nfi8qZ0gQaCPVvW_w8fxbvday3Wgn4dGkh4XXAfefUlSO9hkzN-RwsAeOFba_bGR7vpBqz54C2cBJoGKFtnB/s400/Screen+Shot+2020-04-18+at+4.58.56+PM.png" width="400" /></a></div>
<br />
<br />
<br />
次に <b><i>amplify push</i></b> を実行して、追加した設定をAWS側に反映します。<br />
<br />
<br />
完了するとAWS側では下記のリソースが作成されています。<br />
<br />
- API Gateway<br />
- Lambda<br />
- DynamoDB<br />
<br />
<br />
これらのうち、Lambda関数は自動生成されたコードだと上手く動かず、何をしているのかを理解した上でアプリケーションの要件に合わせてそこそこ手を加える必要がありました。<br />
<br />
<br />
<b>/amplify/backend/function/(Function名)/src/app.js (一部のみ抜粋)</b><br />
-----<br />
<script src="https://gist.github.com/mikehibm/c5f667103304f4c26b3a8437f9168cf6.js"></script><br />
-----<br />
<br />
<br />
アプリケーション側で扱うModelとしては<br />
<br />
<b> {</b><b> </b><br />
<b> id: 'todo:12312-312132',</b><b> </b><br />
<b> text: 'ミルクを買う',</b><b> </b><br />
<b> done: false </b><br />
<b> }</b><br />
<br />
のような形になっているのですが、DynamoDBに保存されるのは、<br />
<br />
<b> {</b><br />
<b> pk: 'user:aaaaaaaaaaa',</b><br />
<b> sk: 'todo:12312-312132',</b><br />
<b> data: {</b><br />
<b> text: 'ミルクを買う',</b><br />
<b> done: false</b><br />
<b> }</b><br />
<b> }</b><br />
<br />
という形になっています。<br />
<br />
このため app.js の内部でAPI Gatewayから受け取ったオブジェクトをDynamoDBのスキーマに合った形式に適宜変換してから保存しています。<br />
<br />
<br />
<br />
さて、次はReactアプリの方を変更して行きます。<br />
<br />
<br />
この辺りのドキュメントを見ながらAPIへのアクセス処理を実装します。<br />
<br />
<a href="https://docs.amplify.aws/lib/restapi/fetch?platform=js#get-requests" target="_blank">Amplify Docs –– Fetching Data</a> <br />
<br />
<br />
<br />
基本的には、<br />
<b><i><br /></i></b>
<b><i>import { API } from 'aws-amplify';</i></b><br />
<b><i><br /></i></b>
でAPIクラスをインポートした後、<br />
<div>
<br />
<b><i>await API.post(apiName, path, options); // CREATE</i></b><br />
<b><i>await API.get(apiName, path, options); // READ</i></b><br />
<b><i>await API.put(apiName, path, options); // UPDATE</i></b><br />
<b><i>await API.del(apiName, path, options); // DELETE</i></b><br />
<br /></div>
<div>
<div>
</div>
</div>
<div>
でLamdaで作成してあるAPIにアクセスすることが出来るので、各アクションからこれらを適宜呼び出しています。</div>
<div>
<br />
<br /></div>
<div>
React側の実装としては、Hooks(useEffect, useReducer)を使って作っています。</div>
<div>
<br />
<br /></div>
<div>
/services/todo-service.js</div>
<div>
-----</div>
<script src="https://gist.github.com/mikehibm/9b644145680882a38aa16fa47950a859.js"></script><br />
<div>
-----</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-9zM-2i8S3I0/Xp1xAeXCqMI/AAAAAAAAZKg/KLidDNkcMo4tnWyS32DXnr6Ve7VJ7zz-ACLcBGAsYHQ/s1600/Screen%2BShot%2B2020-04-19%2Bat%2B11.52.46%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1112" data-original-width="1440" height="308" src="https://1.bp.blogspot.com/-9zM-2i8S3I0/Xp1xAeXCqMI/AAAAAAAAZKg/KLidDNkcMo4tnWyS32DXnr6Ve7VJ7zz-ACLcBGAsYHQ/s400/Screen%2BShot%2B2020-04-19%2Bat%2B11.52.46%2BPM.png" width="400" /></a></div>
<br />
<br />
ここまででなんとかシンプルなTodoリストアプリが完成しました。<br />
<br />
<br />
<br />
<ul>
<li><b>Amplify Consoleによるスタティックなサイトのホスティングは簡単・超便利!</b></li>
</ul>
<div>
<b><br /></b></div>
<ul>
<li><b>Amplify CLIでのユーザー認証機能の追加はパラメータの設定が最初ちょっと大変だけど、何回か試行錯誤して分かってしまえばこちらも簡単で素晴らしい!</b></li>
</ul>
<div>
<b><br /></b></div>
<ul>
<li><b>Amplify CLIでのREST APIの作成は、DynamoDBの特性を理解して設定する事が超重要!(<span style="color: red;">*</span>)</b></li>
</ul>
<br />
<br />
<br />
<span style="color: red;">*</span> もちろんDynamoDB以外の任意のデータベースを使うことも可能。<br />
<br />
<br />
現在のソースコードは下記から確認可能です!<br />
<br />
<a href="https://github.com/mikehibm/amplify-test01">https://github.com/mikehibm/amplify-test01</a><br />
<br />
<br />
<br />
<br class="Apple-interchange-newline" />
1) <a href="https://blog.makotoishida.com/2020/04/nextjsaws-amplify-console.html" target="_blank">Next.jsでスタティック・エクスポートしたサイトをAWS Amplify Consoleでホスティングする</a><br />
<br />
2) <a href="https://blog.makotoishida.com/2020/04/aws-amplifyreact.html" target="_blank">AWS AmplifyでReactアプリにユーザー認証機能を追加する</a><br />
<br />
3) <a href="https://blog.makotoishida.com/2020/04/aws-amplifydynamodbrest-api.html" target="_blank">AWS AmplifyとDynamoDBでサーバーレスなREST APIを構成する</a><br />
<br />
<br />
<br />
<br />
<br />
<br />
Makotohttp://www.blogger.com/profile/09442687702666423250noreply@blogger.comtag:blogger.com,1999:blog-37629199.post-34582058840849467502020-04-17T15:43:00.001+09:002020-04-20T19:41:37.757+09:00AWS AmplifyでReactアプリにユーザー認証機能を追加する<a href="https://blog.makotoishida.com/2020/04/nextjsaws-amplify-console.html" target="_blank">前回</a>はNext.jsでエクスポートしたWebアプリをAmplify Consoleでホスティングするところまで行いました。<br />
<br />
今回はこのアプリに<a href="https://aws.amazon.com/jp/cognito/" target="_blank">AWS Cognito</a>によるユーザー認証を追加したいと思います。<br />
<br />
<br />
<br />
まず <b><i>amplify status</i></b> で現在の状態を確認しておきます。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-Ov1TOAJAuoQ/Xpjc62rHXEI/AAAAAAAAZD0/CgKxuxHDy0gimoU0udrGzIW2uM0m9i-XwCLcBGAsYHQ/s1600/Screen%2BShot%2B2020-04-16%2Bat%2B12.31.28%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="251" data-original-width="1166" height="85" src="https://1.bp.blogspot.com/-Ov1TOAJAuoQ/Xpjc62rHXEI/AAAAAAAAZD0/CgKxuxHDy0gimoU0udrGzIW2uM0m9i-XwCLcBGAsYHQ/s400/Screen%2BShot%2B2020-04-16%2Bat%2B12.31.28%2BPM.png" width="400" /></a></div>
<br />
<br />
次に、<i style="font-weight: bold;"> amplify auth add </i>を実行。<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6WtzskkvgM-tcHpmJ5OKOqIi3GS2SJxAMxNn63Nhg8duzw8mpqUQeq5RF42lkRZFPV2E3Pwgn3FZs1Z2IhXGnCeYMO06_Z8rWAWrAJp1zKmZT52xgPr3gl5ebaM-zOsglS7RH/s1600/Screen+Shot+2020-04-16+at+1.00.25+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="792" data-original-width="1600" height="197" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj6WtzskkvgM-tcHpmJ5OKOqIi3GS2SJxAMxNn63Nhg8duzw8mpqUQeq5RF42lkRZFPV2E3Pwgn3FZs1Z2IhXGnCeYMO06_Z8rWAWrAJp1zKmZT52xgPr3gl5ebaM-zOsglS7RH/s400/Screen+Shot+2020-04-16+at+1.00.25+PM.png" width="400" /></a></div>
<br />
<br />
最初に「Do you want to use the default authentication and security configuration?」と聞かれます。<br />
<br />
ここでデフォルト設定を使って試したところ、サインアップの際にメールアドレスの他に電話番号の入力も必須になってしまったので、それを回避するために今度は「Manual configuration」を選択して次に進みました。<br />
<br />
<br />
次に、<b style="font-style: italic;">amplify push </b>を実行して追加した設定をAWS側に反映します。<br />
<br />
<br />
コマンドが完了すると、AWS側では必要なリソース(Cognito User Pool、Lambdaファンクションなど)が作成されているのが分かります。<br />
<br />
<br />
次にReactアプリケーションの方でCognitoに接続するために、必要なライブラリを追加します。<br />
<br />
<blockquote class="tr_bq">
<b><i>npm i aws-amplify @aws-amplify/ui-react</i></b></blockquote>
<br />
<br />
ログイン画面の表示には「<a href="https://aws-amplify.github.io/docs/js/ui-components" target="_blank">Amplify UI Components</a>」を使います。<br />
<br />
/pagesフォルダ内にlogin.jsを作成し下のコードを入力しました。<br />
<br />
----------<br />
<script src="https://gist.github.com/mikehibm/726f0f97ba2803641481d31150aefd15.js"></script><br />
----------<br />
<br />
これだけで、http://localhost:3000/login を開くと下のようなログイン画面が表示されるようになります。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5q87Ll4Rh0ML5dDshB3XspjiswMcoVzAwZg-j_04iokB_5ShSWjgc0MvySbq6BPYc3MBYnttLKU5jx-LtpzW6nUMvCxJoet-vhXKDCIyX8kTWnfZSbxeKOuCIBUS2kW0V25Hr/s1600/localhost_3000_login%2528iPhone+6_7_8%2529.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1334" data-original-width="750" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5q87Ll4Rh0ML5dDshB3XspjiswMcoVzAwZg-j_04iokB_5ShSWjgc0MvySbq6BPYc3MBYnttLKU5jx-LtpzW6nUMvCxJoet-vhXKDCIyX8kTWnfZSbxeKOuCIBUS2kW0V25Hr/s400/localhost_3000_login%2528iPhone+6_7_8%2529.png" width="223" /></a></div>
<br />
<br />
もちろん最初はユーザー登録が必要なので、「Create account」のリンクをクリックします。(この辺りの文言はカスタマイズ可能です。)<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-q5LnQ9oZiBU/XplJNYFcbHI/AAAAAAAAZGQ/X6rBgFG3Rc07cJlLbuhOFp0TaAPUNJ_TACLcBGAsYHQ/s1600/localhost_3000_login%2528iPhone%2B6_7_8%2529%2B%25281%2529.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1334" data-original-width="750" height="400" src="https://1.bp.blogspot.com/-q5LnQ9oZiBU/XplJNYFcbHI/AAAAAAAAZGQ/X6rBgFG3Rc07cJlLbuhOFp0TaAPUNJ_TACLcBGAsYHQ/s400/localhost_3000_login%2528iPhone%2B6_7_8%2529%2B%25281%2529.png" width="223" /></a></div>
<br />
ここで、電話番号の入力欄が必須となってしまっています。<br />
これを非表示にするためのカスタマイズ方法が<a href="https://docs.amplify.aws/ui/auth/authenticator?framework=react#sign-up" target="_blank">ドキュメント</a>には載っているのですが、その通りにやってみても上手く行きませんでした。<br />
<br />
検索したところ、UI Componentのバグとして<a href="https://github.com/aws-amplify/amplify-js/issues/5298" target="_blank">GitHubのIssue</a>に上がっているようです。ちょうどこの記事を書いている数時間前にFixがマージされているみたいなので、近日中には解決しそうです。今はとりあえずダミーの電話番号を入力して次へ進みます。<br />
<br />
<br />
登録フォームをSubmitすると、確認コードの入力画面に移ります。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-yFwawhOPrk0/XplK4kGW5QI/AAAAAAAAZGY/g14JhqSeLLsEHkYX3fUnod_yISc91WyRgCLcBGAsYHQ/s1600/localhost_3000_login%2528iPhone%2B6_7_8%2529%2B%25282%2529.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1334" data-original-width="750" height="400" src="https://1.bp.blogspot.com/-yFwawhOPrk0/XplK4kGW5QI/AAAAAAAAZGY/g14JhqSeLLsEHkYX3fUnod_yISc91WyRgCLcBGAsYHQ/s400/localhost_3000_login%2528iPhone%2B6_7_8%2529%2B%25282%2529.png" width="223" /></a></div>
<br />
<br />
<br />
入力したメールアドレスに確認コードが送られているので、そのコードを入力して「Confirm」をクリックすれば登録完了です。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMcOVZNNP4ZbvISMIdA22MN3uaL7OvPV4m5Rxnchh56HMZh8iT7oR2C8K9VRbuRQ2DgpSkQox-KrkYZchNrYShqyqUuRiKmPizJC-rez7cGNzc1S4fpk3GiP1kH0auFSUyYZVZ/s1600/Screen+Shot+2020-04-16+at+8.43.01+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="778" data-original-width="738" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMcOVZNNP4ZbvISMIdA22MN3uaL7OvPV4m5Rxnchh56HMZh8iT7oR2C8K9VRbuRQ2DgpSkQox-KrkYZchNrYShqyqUuRiKmPizJC-rez7cGNzc1S4fpk3GiP1kH0auFSUyYZVZ/s200/Screen+Shot+2020-04-16+at+8.43.01+PM.png" width="189" /></a></div>
<br />
<br />
この時点でAWSコンソールでCognitoのUser Poolを見てみると、確かに1件のユーザー情報が登録されているのが分かります。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg55_MfdAxlrDq6tTi9D0zM8bRY11W3ZnN1u_8ZDIFHKFKSnpJxzUkD48kymw7kScz7BWOmYsiTNVrJ9neV3Oa8zKGBmy5mKnOKfeSlUYoxPLYpUqgkOSgUCmC9B0lDUjMcAUrG/s1600/Screen+Shot+2020-04-16+at+8.39.26+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="580" data-original-width="1504" height="123" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg55_MfdAxlrDq6tTi9D0zM8bRY11W3ZnN1u_8ZDIFHKFKSnpJxzUkD48kymw7kScz7BWOmYsiTNVrJ9neV3Oa8zKGBmy5mKnOKfeSlUYoxPLYpUqgkOSgUCmC9B0lDUjMcAUrG/s320/Screen+Shot+2020-04-16+at+8.39.26+PM.png" width="320" /></a></div>
<br />
バックエンド側を何も作り込んでいないのにここまで自動的に出来上がってしまうのはすごいですね!<br />
<br />
<br />
最後に上で作成したlogin.jsと同様に認証用のUIをindex.jsページにも追加します。<br />
<br />
-----<br />
<script src="https://gist.github.com/mikehibm/937556128f076b00670fb4af18ffc95a.js"></script><br />
-----<br />
<br />
ここではサインアウトボタンは要らないので削除し、代わりに右上のヘッダー内に「Sign Out」リンクを追加ししました。<br />
<br />
サインアウトの処理を実装するには、<br />
<br />
<blockquote class="tr_bq">
<b><i>import { Auth } from 'aws-amplify';</i></b></blockquote>
<br />
<div>
でAuthクラスをインポートし、リンクがクリックされたら</div>
<div>
<br /></div>
<blockquote class="tr_bq">
<b><i>Auth.signOut();</i></b></blockquote>
<div>
<br /></div>
<div>
を実行すればOKです。</div>
<div>
<br />
<br />
<br />
<a href="https://blog.makotoishida.com/2020/04/aws-amplifydynamodbrest-api.html" target="_blank">次回</a>はバックエンド側のREST APIをAmplifyのサーバーレスな機能を使って追加します!<br />
<br />
<a href="https://blog.makotoishida.com/2020/04/aws-amplifydynamodbrest-api.html" target="_blank">AWS AmplifyとDynamoDBでサーバーレスなREST APIを構成する </a><br />
<br />
<br />
<br /></div>
<div>
<br /></div>
<div>
ここまでのソースコードはこちらで確認出来ます!</div>
<div>
<br /></div>
<div>
<a href="https://github.com/mikehibm/amplify-test01/tree/blog-2020-04-17" target="_blank">https://github.com/mikehibm/amplify-test01/tree/blog-2020-04-17</a></div>
<div>
<br /></div>
<div>
<br /></div>
<br />
<br />
<br />
Makotohttp://www.blogger.com/profile/09442687702666423250noreply@blogger.comtag:blogger.com,1999:blog-37629199.post-18986372362107237072020-04-16T14:11:00.000+09:002020-04-20T19:35:03.890+09:00Next.jsでスタティック・エクスポートしたサイトをAWS Amplify Consoleでホスティングする今回は、Next.jsから(SSR無しの)スタティックなSPAとしてエクスポートしたサイトを、Amplify Consoleでホスティングしてみます。<br />
<br />
<br />
<h2>
サンプルReactアプリの作成</h2>
<br />
<blockquote class="tr_bq">
<b><i>npm init -y<br />npm i -S react react-dom next</i> </b></blockquote>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhc0539k8lKDtMtlVPDPYdiE1bZHvL5zrLPtHqzR6s_WnZ8WTRyTv0YAzFVEd-ArhR27DR1JhMoO3gz6evbD5TqRDi_zeYSkFNvnUk_j3qNoB8jlU1YZ2tt_x-JjoeUirSDcrO5/s1600/Screen+Shot+2020-04-15+at+6.47.45+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="806" data-original-width="1450" height="220" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhc0539k8lKDtMtlVPDPYdiE1bZHvL5zrLPtHqzR6s_WnZ8WTRyTv0YAzFVEd-ArhR27DR1JhMoO3gz6evbD5TqRDi_zeYSkFNvnUk_j3qNoB8jlU1YZ2tt_x-JjoeUirSDcrO5/s400/Screen+Shot+2020-04-15+at+6.47.45+PM.png" width="400" /></a></div>
<br />
package.jsonはこんな感じになっています。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-hVsecuRwKEc/XpfpYh5_nnI/AAAAAAAAZDM/Oc_Nn4AHCxw254R52HZRjrcG9Z0hE41SACLcBGAsYHQ/s1600/Screen%2BShot%2B2020-04-15%2Bat%2B7.12.47%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="776" data-original-width="1124" height="220" src="https://1.bp.blogspot.com/-hVsecuRwKEc/XpfpYh5_nnI/AAAAAAAAZDM/Oc_Nn4AHCxw254R52HZRjrcG9Z0hE41SACLcBGAsYHQ/s320/Screen%2BShot%2B2020-04-15%2Bat%2B7.12.47%2BPM.png" width="320" /></a></div>
<br />
<br />
<br />
<b><i>npm run dev</i></b> を実行すると開発サーバーが立ち上がるのでブラウザで http://localhost:3000 を開きます。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-aavIRahiRxo/Xpfjt97zpwI/AAAAAAAAZC8/nUCtWdxzVl0aa9yLa2Ss7l7L3ynVIclagCLcBGAsYHQ/s1600/Screen%2BShot%2B2020-04-15%2Bat%2B6.48.42%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="430" data-original-width="804" height="171" src="https://1.bp.blogspot.com/-aavIRahiRxo/Xpfjt97zpwI/AAAAAAAAZC8/nUCtWdxzVl0aa9yLa2Ss7l7L3ynVIclagCLcBGAsYHQ/s320/Screen%2BShot%2B2020-04-15%2Bat%2B6.48.42%2BPM.png" width="320" /></a></div>
<br />
<b><i>npm run export</i></b> を実行すると out フォルダにindex.htmlや404.html などのファイルが出力されます。今回はこれらの静的なHTMLファイルを Amplify Consoleでホスティングすることまで出来れば目的達成です。<br />
<br />
<br />
<h2>
amplify-cliのインストール</h2>
<br />
<blockquote class="tr_bq">
<b><i>npm i -g amplify-cli</i></b></blockquote>
<br />
<h2>
Amplifyの初期化</h2>
<br />
<blockquote class="tr_bq">
<b><i>amplify configure</i></b></blockquote>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-p-uxxgWc6BE/XpfbeEZn4KI/AAAAAAAAZBQ/LcfcnT55nBMQHmmXbiBfIiOdIqPXLChFgCLcBGAsYHQ/s1600/Screen%2BShot%2B2020-04-15%2Bat%2B6.13.25%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1115" data-original-width="1600" height="223" src="https://1.bp.blogspot.com/-p-uxxgWc6BE/XpfbeEZn4KI/AAAAAAAAZBQ/LcfcnT55nBMQHmmXbiBfIiOdIqPXLChFgCLcBGAsYHQ/s320/Screen%2BShot%2B2020-04-15%2Bat%2B6.13.25%2BPM.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/--qNdekkuf2A/XpfbnF9-j7I/AAAAAAAAZBU/47Cbu62wxQYVgl0WJ7-HVuIkmPCoHMoYACLcBGAsYHQ/s1600/Screen%2BShot%2B2020-04-15%2Bat%2B6.14.08%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1085" data-original-width="1600" height="217" src="https://1.bp.blogspot.com/--qNdekkuf2A/XpfbnF9-j7I/AAAAAAAAZBU/47Cbu62wxQYVgl0WJ7-HVuIkmPCoHMoYACLcBGAsYHQ/s320/Screen%2BShot%2B2020-04-15%2Bat%2B6.14.08%2BPM.png" width="320" /></a></div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-9Pd_zMsk4Rs/XpfcBqQmwCI/AAAAAAAAZBY/xY5Vl51RR_wBMFGZxtFmZS_cmYFagjJUACLcBGAsYHQ/s1600/Screen%2BShot%2B2020-04-15%2Bat%2B6.14.57%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="783" data-original-width="1600" height="156" src="https://1.bp.blogspot.com/-9Pd_zMsk4Rs/XpfcBqQmwCI/AAAAAAAAZBY/xY5Vl51RR_wBMFGZxtFmZS_cmYFagjJUACLcBGAsYHQ/s320/Screen%2BShot%2B2020-04-15%2Bat%2B6.14.57%2BPM.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXWR3szA5dNjg1chOsVxR7pz2bcpn9qVqGVlU_y0NrRpQt36_VX4VOyggp6sj6VrYzPHOVgqJGMd1WdrSQjFn01434DkpeGWf4oyUaF64aUwggRxfoOyifANgIw8GUmNCoLjEq/s1600/Screen+Shot+2020-04-15+at+6.17.19+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="440" data-original-width="1326" height="105" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgXWR3szA5dNjg1chOsVxR7pz2bcpn9qVqGVlU_y0NrRpQt36_VX4VOyggp6sj6VrYzPHOVgqJGMd1WdrSQjFn01434DkpeGWf4oyUaF64aUwggRxfoOyifANgIw8GUmNCoLjEq/s320/Screen+Shot+2020-04-15+at+6.17.19+PM.png" width="320" /></a></div>
<br />
<br />
<blockquote class="tr_bq">
<b><i>amplify init</i></b></blockquote>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-wHfqLvRpdwI/Xpfdh7XFxjI/AAAAAAAAZBw/veSQPnN-biQVU6mTLPtE3GT-vpZg2i4MQCLcBGAsYHQ/s1600/Screen%2BShot%2B2020-04-15%2Bat%2B6.21.31%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="658" data-original-width="1490" height="141" src="https://1.bp.blogspot.com/-wHfqLvRpdwI/Xpfdh7XFxjI/AAAAAAAAZBw/veSQPnN-biQVU6mTLPtE3GT-vpZg2i4MQCLcBGAsYHQ/s320/Screen%2BShot%2B2020-04-15%2Bat%2B6.21.31%2BPM.png" width="320" /></a></div>
<br />
<br />
<h2>
Amplify Consoleでのホスティング設定を追加</h2>
<br />
<blockquote class="tr_bq">
<b><i>amplify hosting add</i></b></blockquote>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhcJxGPSfQtVs1ek5ho-AYhDIf4De0hcCuAJ3W92amh4ihpByQ7Qm9nC-95zJVrXIeNIpNhFoa3Bn4u3xpuUW8vmhN6z0rD1B2rXnGtpgVcAINk554ok1BErk1xIDIf5CDB7Dxl/s1600/Screen+Shot+2020-04-15+at+6.23.28+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="140" data-original-width="1300" height="42" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhcJxGPSfQtVs1ek5ho-AYhDIf4De0hcCuAJ3W92amh4ihpByQ7Qm9nC-95zJVrXIeNIpNhFoa3Bn4u3xpuUW8vmhN6z0rD1B2rXnGtpgVcAINk554ok1BErk1xIDIf5CDB7Dxl/s400/Screen+Shot+2020-04-15+at+6.23.28+PM.png" width="400" /></a></div>
<br />
上のコマンドを実行して「Continuous deployment...」を選ぶと、ブラウザが開いて接続するGitリポジトリを聞かれます。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgElF6GOjny987G_yfBkMp4N00r5Y154Ivh-g8Yudbk0Tz4-7QbzYT0HYp0Ke1JnBkJ4ZWqvnDSO_nQ5A_uvTFkwi_3FGabHAHYKqcKVwpeYxszkFmPO0gM1Ga0A1OaJvyfM45Q/s1600/Screen+Shot+2020-04-15+at+6.24.05+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1066" data-original-width="1600" height="213" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgElF6GOjny987G_yfBkMp4N00r5Y154Ivh-g8Yudbk0Tz4-7QbzYT0HYp0Ke1JnBkJ4ZWqvnDSO_nQ5A_uvTFkwi_3FGabHAHYKqcKVwpeYxszkFmPO0gM1Ga0A1OaJvyfM45Q/s320/Screen+Shot+2020-04-15+at+6.24.05+PM.png" width="320" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiy6CCYFWpCirXJCTGgaICk7FWMIGtnf5Q-SHU7i7OkwLjLn2D3NlI1kNOgrBJECeCdPlRPYuZXyM1_RSm1eLauLGw0sdXZX_gRNBQBmzJAjoRiS3Ej9dvgnx53wvtmT89B6BRW/s1600/Screen+Shot+2020-04-15+at+6.25.00+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="858" data-original-width="1600" height="171" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiy6CCYFWpCirXJCTGgaICk7FWMIGtnf5Q-SHU7i7OkwLjLn2D3NlI1kNOgrBJECeCdPlRPYuZXyM1_RSm1eLauLGw0sdXZX_gRNBQBmzJAjoRiS3Ej9dvgnx53wvtmT89B6BRW/s320/Screen+Shot+2020-04-15+at+6.25.00+PM.png" width="320" /></a></div>
<br />
amplify.yml という設定ファイルを手動で変更する必要があるので、ダウンロードしてプロジェクトのルートフォルダに保存します。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-4Bgb4K5Fl3U/Xpfee7fJqfI/AAAAAAAAZCQ/kddXn5U1PxET8fcVVKZAmlo8VNwcyf4MgCLcBGAsYHQ/s1600/Screen%2BShot%2B2020-04-15%2Bat%2B6.26.15%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1248" data-original-width="1600" height="249" src="https://1.bp.blogspot.com/-4Bgb4K5Fl3U/Xpfee7fJqfI/AAAAAAAAZCQ/kddXn5U1PxET8fcVVKZAmlo8VNwcyf4MgCLcBGAsYHQ/s320/Screen%2BShot%2B2020-04-15%2Bat%2B6.26.15%2BPM.png" width="320" /></a></div>
<br />
ここではNext.jsでスタティック・エクスポートした成果物をデプロイしたいので、下のように一部を変更します。(commandsとbaseDirectory)<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-8YjGsMpmjVM/XpfpubKznII/AAAAAAAAZDU/-908vwfwJgkzde6jI03G6MKBJWOvI6ZtQCLcBGAsYHQ/s1600/Screen%2BShot%2B2020-04-15%2Bat%2B7.13.43%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="630" data-original-width="668" height="301" src="https://1.bp.blogspot.com/-8YjGsMpmjVM/XpfpubKznII/AAAAAAAAZDU/-908vwfwJgkzde6jI03G6MKBJWOvI6ZtQCLcBGAsYHQ/s320/Screen%2BShot%2B2020-04-15%2Bat%2B7.13.43%2BPM.png" width="320" /></a></div>
<br />
<br />
<br />
変更したamplify.ymlファイルをGitリポジトリにコミット・プッシュします。<br />
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
プッシュした時点で自動的にAmplify Console側でビルド処理が走ります。</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-3WLfezQi-pk/XpffjwqabPI/AAAAAAAAZCc/VKlOQiFkyw0wHl5MBCo3G2YvLsMBBGDfQCLcBGAsYHQ/s1600/Screen%2BShot%2B2020-04-15%2Bat%2B6.31.00%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="594" data-original-width="1002" height="189" src="https://1.bp.blogspot.com/-3WLfezQi-pk/XpffjwqabPI/AAAAAAAAZCc/VKlOQiFkyw0wHl5MBCo3G2YvLsMBBGDfQCLcBGAsYHQ/s320/Screen%2BShot%2B2020-04-15%2Bat%2B6.31.00%2BPM.png" width="320" /></a></div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSHdBi8UGjvRLCQmJYQEtKoCZzmYRvYwrM2yNiuKKO5QuEFzn5QByXZdWUqm2q6suxHt3EKqDSoPMdomr1vBSObC0qtYBo4WLoZUf4C9Urn5Sx8Z5pYHDdPb0Hdr_weEbdkd-2/s1600/Screen+Shot+2020-04-15+at+6.35.13+PM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="512" data-original-width="940" height="174" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSHdBi8UGjvRLCQmJYQEtKoCZzmYRvYwrM2yNiuKKO5QuEFzn5QByXZdWUqm2q6suxHt3EKqDSoPMdomr1vBSObC0qtYBo4WLoZUf4C9Urn5Sx8Z5pYHDdPb0Hdr_weEbdkd-2/s320/Screen+Shot+2020-04-15+at+6.35.13+PM.png" width="320" /></a></div>
<br />
<br />
ProvisionからVerifyまで全てグリーンになってから画面に表示されているURLをクリックすると、アプリケーションがデプロイされているのが確認出来ます。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-FHKEGqUsmZs/Xpfgv-gGLJI/AAAAAAAAZCo/x1CWvNLJ0rIXnyls9TofXZXMPSI5RssqgCLcBGAsYHQ/s1600/Screen%2BShot%2B2020-04-15%2Bat%2B6.36.05%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="492" data-original-width="974" height="161" src="https://1.bp.blogspot.com/-FHKEGqUsmZs/Xpfgv-gGLJI/AAAAAAAAZCo/x1CWvNLJ0rIXnyls9TofXZXMPSI5RssqgCLcBGAsYHQ/s320/Screen%2BShot%2B2020-04-15%2Bat%2B6.36.05%2BPM.png" width="320" /></a></div>
<br />
今は「Hello!」と表示するだけで何もしないアプリケーションですが、後は通常のReactアプリケーションとして「npm run dev」で実行しながらローカルで開発します。<br />
<br />
<br />
<br />
<br />
ひとまずブラウザ上だけで動くTodoアプリを作りました。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-bRgFg-Lgh7E/XpgUHY9B7cI/AAAAAAAAZDk/_1mut1zLrqQKEBTpW-IPTxWdbjnJxxTzQCLcBGAsYHQ/s1600/Screen%2BShot%2B2020-04-15%2Bat%2B10.14.38%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1098" data-original-width="992" height="400" src="https://1.bp.blogspot.com/-bRgFg-Lgh7E/XpgUHY9B7cI/AAAAAAAAZDk/_1mut1zLrqQKEBTpW-IPTxWdbjnJxxTzQCLcBGAsYHQ/s400/Screen%2BShot%2B2020-04-15%2Bat%2B10.14.38%2BPM.png" width="361" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<br />
<br />
このままではブラウザを閉じるとデータが消えてしまうので意味が無いですが、<a href="https://blog.makotoishida.com/2020/04/aws-amplifyreact.html" target="_blank">次回</a>以降のエントリではここに「<a href="https://blog.makotoishida.com/2020/04/aws-amplifyreact.html" target="_blank">ユーザー認証</a>」と「<a href="https://blog.makotoishida.com/2020/04/aws-amplifydynamodbrest-api.html" target="_blank">REST API経由でのDynamoDBへのデータ永続化機能</a>」を追加して行こうと思います。<br />
<br />
<br />
<a href="https://blog.makotoishida.com/2020/04/aws-amplifyreact.html" target="_blank">AWS AmplifyでReactアプリにユーザー認証機能を追加する</a><br />
<br />
<a href="https://blog.makotoishida.com/2020/04/aws-amplifydynamodbrest-api.html" target="_blank">AWS AmplifyとDynamoDBでサーバーレスなREST APIを構成する</a><br />
<br />
<br />
<br />
<br />
現在のソースコードはこちらで参照出来ます!<br />
<br />
<a href="https://github.com/mikehibm/amplify-test01/tree/blog-2020-04-15" target="_blank">https://github.com/mikehibm/amplify-test01/tree/blog-2020-04-15</a><br />
<br />
<br />
<br />
<br />
<br />
<br />
Makotohttp://www.blogger.com/profile/09442687702666423250noreply@blogger.comtag:blogger.com,1999:blog-37629199.post-52021149701523967472020-04-14T19:14:00.001+09:002020-04-20T19:54:14.673+09:00AWS AmplifyでReactアプリ開発を試してみた時間があったので前から気になっていた <a href="https://aws.amazon.com/jp/amplify/" rel="nofollow" target="_blank">AWS Amplify</a> を試してみることにしました。<br />
<br />
とりあえず作ったのは、Todoリスト。React (Next.js) によるWebアプリです。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-0WCG4c2phDg/XpWEScbK7nI/AAAAAAAAZA8/PIvlOSTtjFEYSXb0FamrVYDFjq4om0s8QCLcBGAsYHQ/s1600/Screen%2BShot%2B2020-04-13%2Bat%2B11.36.55%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1300" data-original-width="1138" height="320" src="https://1.bp.blogspot.com/-0WCG4c2phDg/XpWEScbK7nI/AAAAAAAAZA8/PIvlOSTtjFEYSXb0FamrVYDFjq4om0s8QCLcBGAsYHQ/s320/Screen%2BShot%2B2020-04-13%2Bat%2B11.36.55%2BPM.png" width="280" /></a></div>
<br />
<br />
username / password でログインすると、DynamoDBに保存された自分のTodoリストが表示されます。<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://1.bp.blogspot.com/-B6NlzXK-9jE/XpWCMAPMQWI/AAAAAAAAZAw/xhDmAs68eDY02kVyLqHomUnBwDxbL5gLgCLcBGAsYHQ/s1600/Screen%2BShot%2B2020-04-13%2Bat%2B11.27.14%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1351" data-original-width="1600" height="270" src="https://1.bp.blogspot.com/-B6NlzXK-9jE/XpWCMAPMQWI/AAAAAAAAZAw/xhDmAs68eDY02kVyLqHomUnBwDxbL5gLgCLcBGAsYHQ/s320/Screen%2BShot%2B2020-04-13%2Bat%2B11.27.14%2BPM.png" width="320" /></a></div>
<br />
<br />
使った技術は、<br />
<br />
<b>フロントエンド</b><br />
- Next.js<br />
- Amplify Console (Hosting)<br />
<br />
<b>バックエンド</b><br />
- Lambda<br />
- API Gateway<br />
- DynamoDB<br />
- Cognito<br />
<br />
という構成になりました。<br />
<br />
<br />
至ってシンプルなTodoリストですが、Cognitoによるユーザー認証やDynamoDBなど慣れないモノについて使い方を調べながら作ったので結構時間がかかりました。<br />
<br />
Amplify ConsoleによるフロントエンドのHosting機能は非常に便利で使いやすいと思いました。<br />
<br />
ただ、今のところ全体的な感想としては 「Firebase の方が使いやすいかなあ」という感じです。特に<a href="https://aws-amplify.github.io/docs/js/react" rel="nofollow" target="_blank">ドキュメント</a>が(内容的には非常に多岐に渡って網羅されているのですが)今ひとつ分かりにくい気がします。<br />
<br />
<br />
これからせっかくなのでもう少し機能追加をしながら使い込んで見たいと思います。<br />
<br />
<br />
1) <a href="https://blog.makotoishida.com/2020/04/nextjsaws-amplify-console.html" target="_blank">Next.jsでスタティック・エクスポートしたサイトをAWS Amplify Consoleでホスティングする</a><br />
<br />
2) <a href="https://blog.makotoishida.com/2020/04/aws-amplifyreact.html" target="_blank">AWS AmplifyでReactアプリにユーザー認証機能を追加する</a><br />
<br />
3) <a href="https://blog.makotoishida.com/2020/04/aws-amplifydynamodbrest-api.html" target="_blank">AWS AmplifyとDynamoDBでサーバーレスなREST APIを構成する</a><br />
<br />
<br />
<br />
<br />
<br />
<br />
Makotohttp://www.blogger.com/profile/09442687702666423250noreply@blogger.comtag:blogger.com,1999:blog-37629199.post-10046148589646271372020-03-05T12:21:00.001+09:002020-03-05T12:25:03.838+09:00iOSアプリにFirebase SDKを追加後、「symbol(s) not found for architecture x86_64」エラーでビルド出来ない場合の対処方法<div>
iOSアプリに「Firebase SDK」を追加した後、「symbol(s) not found for architecture x86_64」エラーでビルド出来なくなるという問題に遭遇しました。<br />
<br />
<br /></div>
<div style="color: black; font-family: "Hiragino Kaku Gothic Pro"; font-size: medium; white-space: normal;">
</div>
<div style="color: black; font-family: "Hiragino Kaku Gothic Pro"; font-size: medium; white-space: normal;">
また数ヶ月後ぐらいに同じ問題にぶつかりそうな気がするので、再度ググらなくても良いように解決策をメモしておきます。<br />
<br />
<br /></div>
<div style="color: black; font-family: "Hiragino Kaku Gothic Pro"; font-size: medium; white-space: normal;">
</div>
<div class="separator" style="clear: both; color: black; font-family: "Hiragino Kaku Gothic Pro"; font-size: medium; text-align: center; white-space: normal;">
<a href="https://1.bp.blogspot.com/-n6uJpzHz8t8/XmBwEFlfApI/AAAAAAAAYw8/sp15BUkH5V0KDF4LKUEG2TWl3sIV4m5dACLcBGAsYHQ/s1600/Screen%2BShot%2B2020-03-04%2Bat%2B5.18.13%2BPM.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="704" data-original-width="1600" height="175" src="https://1.bp.blogspot.com/-n6uJpzHz8t8/XmBwEFlfApI/AAAAAAAAYw8/sp15BUkH5V0KDF4LKUEG2TWl3sIV4m5dACLcBGAsYHQ/s400/Screen%2BShot%2B2020-03-04%2Bat%2B5.18.13%2BPM.png" width="400" /></a></div>
<div class="separator" style="clear: both; color: black; font-family: "Hiragino Kaku Gothic Pro"; font-size: medium; text-align: center; white-space: normal;">
<br /></div>
<div class="separator" style="clear: both; color: black; font-family: "Hiragino Kaku Gothic Pro"; font-size: medium; text-align: center; white-space: normal;">
<br /></div>
<div style="color: black; font-family: "Hiragino Kaku Gothic Pro"; font-size: medium; white-space: normal;">
</div>
<div style="color: black; font-family: "Hiragino Kaku Gothic Pro"; font-size: medium; white-space: normal;">
</div>
<blockquote class="tr_bq" style="color: black; font-family: "Hiragino Kaku Gothic Pro"; font-size: medium; white-space: normal;">
> Go to your Target Build Settings -> Other linker flags -> double click -> "+" button. -> Add $(inherited) to a new line.</blockquote>
<blockquote class="tr_bq" style="color: black; font-family: "Hiragino Kaku Gothic Pro"; font-size: medium; white-space: normal;">
<a href="https://stackoverflow.com/questions/42292090/firebase-undefined-symbols-for-architecture-x86-64" target="_blank">ios - Firebase Undefined symbols for architecture x86_64 - Stack Overflow</a></blockquote>
<div style="color: black; font-family: "Hiragino Kaku Gothic Pro"; font-size: medium; white-space: normal;">
</div>
<div style="color: black; font-family: "Hiragino Kaku Gothic Pro"; font-size: medium; white-space: normal;">
<br />
<br />
<br />
<br />
</div>
Makotohttp://www.blogger.com/profile/09442687702666423250noreply@blogger.com