tag:crieit.net,2005:https://crieit.net/tags/Kotlin/feed 「Kotlin」の記事 - Crieit Crieitでタグ「Kotlin」に投稿された最近の記事 2021-01-15T08:49:14+09:00 https://crieit.net/tags/Kotlin/feed tag:crieit.net,2005:PublicArticle/16602 2021-01-15T05:11:53+09:00 2021-01-15T08:49:14+09:00 https://crieit.net/posts/Kotlin-6000a589e43c6 素朴な自作言語のコンパイラをKotlinに移植した <hr /> <p><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/30/132314">移植一覧に戻る</a></p> <hr /> <p><a href="https://crieit.now.sh/upload_images/8c8511d3a32db4f8b9038c1702b366546000a3e7ba0b2.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/8c8511d3a32db4f8b9038c1702b366546000a3e7ba0b2.png?mw=700" alt="image" /></a></p> <p>Kotlin に移植してみました。やっつけなので汚いです。ライフゲームのコンパイルが通ったのでヨシ、というレベルのものです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-kotlin">https://github.com/sonota88/vm2gol-v2-kotlin</a></p> <h1 id="移植元"><a href="#%E7%A7%BB%E6%A4%8D%E5%85%83">移植元</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/05/04/155425">Rubyで素朴な自作言語のコンパイラを作った</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2019/05/04/232236">RubyでオレオレVMとアセンブラとコード生成器を2週間で作ってライフゲームを動かした話</a></li> </ul> <p>ベースになっているバージョン: <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2/tree/51">tag:51</a> のあたり</p> <h1 id="メモ"><a href="#%E3%83%A1%E3%83%A2">メモ</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/30/121437">Java版</a> からコピーしてきて修正してできあがり。3日くらい。</li> <li>Kotlin は仕事で少しだけ使って、あと少し前に 「<a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/07/11/135045">標準入力を読んで行ごとに処理</a>」と「<a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/07/11/120000">四則演算と剰余のみのexprコマンドをKotlinで作ってみた</a>」を書いたくらいのほぼ初心者</li> <li>Java と Scala の中間みたいな印象</li> </ul> <hr /> <p>今回の実験は配列変数の宣言。<a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-kotlin/compare/main...trial">trial ブランチ</a>で試してみました。</p> <p>確認用のコードです。今回の実験ではとりあえずこれだけコンパイルできれば OK。</p> <pre><code class="javascript">// declare_array.vg.txt func main() { var xs[22]; } </code></pre> <p>AST がこんな感じ。お試しなので適当。</p> <pre><code class="sh">$ ( \ > cat declare_array.vg.txt \ > | ./run.sh tokenize \ > | ./run.sh parse \ > ) 2>/dev/null [ "top_stmts", [ "func", "main", [ ], [ [ "var_array", "xs", 22 ] ] ] ] </code></pre> <p>アセンブリコードがこんな感じ。</p> <pre><code class="sh">$ ( \ > cat declare_array.vg.txt \ > | ./run.sh tokenize \ > | ./run.sh parse \ > | ./run.sh codegen \ > ) 2>/dev/null call main exit label main push bp cp sp bp # 関数の処理本体 sub_sp 22 # ... 配列のサイズ分の領域を確保する cp bp sp pop bp ret </code></pre> <p>これだけだったら少し修正するだけで済みました。</p> <p>次のようなコードはまだ正しく動くようにコンパイルできません。</p> <pre><code class="javascript">func main() { var xs[22]; var a = 11; } </code></pre> <p>配列がスタック上で占有している幅を考慮しないといけないのに、それをまだやってないからです。これをやろうとするとたぶんあちこち修正しないといけなくなるので、それはまた今度ということで。</p> sonota486 tag:crieit.net,2005:PublicArticle/15893 2020-05-09T14:43:35+09:00 2020-05-09T17:35:53+09:00 https://crieit.net/posts/Flutter-Android Flutterのネイティブで重い処理をさせる方法(Android) <p>Flutterを使ってAndroidのネイティブで重い処理をさせているとおかしくなることがあった。具体的にはFutureBuilderを使っている時に気づいたのだが、いつまでたってもFutureBuilderが完了状態にならない。</p> <p>そういえば以前から下記のようなログが出ていることを思い出した。</p> <pre><code class="text">Skipped 229 frames! The application may be doing too much work on its main thread </code></pre> <p>これはメインスレッドでの処理が重すぎるから途中でやめちゃったよ、というAndroid側のメッセージ。ということで、この処理を別スレッドで実行してあげる必要がある。例えば元々下記のような処理を行っているとする。</p> <pre><code class="kotlin">if (call.method == "heavy") { myInstance.heavyAction() result.success(true) } </code></pre> <p>上記を別スレッドにする必要がある。あまりここに色々詰め込みたくなかったのでメソッド内に記述した。</p> <pre><code class="kotlin">fun heavyAction(callback: () -> Unit) { thread { heavy() callback() } } </code></pre> <p>呼び出し側</p> <pre><code class="kotlin">if (call.method == "heavy") { myInstance.heavyAction() { result.success(true) } } </code></pre> <p>これで良さそうに思うのだが、今度は下記のエラーが出る。</p> <pre><code class="text">Methods marked with @UiThread must be executed on the main thread </code></pre> <p>完了処理を別スレッドで行わず、メインUIスレッドで行なえ、とのこと。</p> <p>ということで最終的に呼び出し元は下記のようになった。</p> <pre><code class="kotlin">if (call.method == "heavy") { myInstance.heavyAction() { runOnUiThread { result.success(true) } } } </code></pre> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/15821 2020-04-12T16:25:35+09:00 2020-04-12T16:25:35+09:00 https://crieit.net/posts/data-class-JSON 単一プロパティを持つdata classをJSONのプリミティブ型として表現する <p>Value Objectとして使っている、単一プロパティを持つdata classを、シンプルなJSONとして表現するのにちょっと手こずったのでまとめておきます。</p> <h2 id="問題設定"><a href="#%E5%95%8F%E9%A1%8C%E8%A8%AD%E5%AE%9A">問題設定</a></h2> <p>例として、次のようなクラス定義を考えます。</p> <pre><code class="kt">data class User(val userId: UserId, val userName: UserName) data class UserId(val value: Int) data class UserName(val value: String) </code></pre> <p>UserオブジェクトをJacksonでシリアライズした場合、普通だと次のようになります。</p> <pre><code class="json">{"userId": {"value": 1}, "userName": {"value": "sato"<span>}</span><span>}</span> </code></pre> <p>これを次のようにシリアライズした上で、元の形にデシリアライズしたいというのが、この記事の主題です。</p> <pre><code class="json">{"userId": 1, "userName": "sato"} </code></pre> <p>なお、 <code>UserId</code> や <code>UserName</code> は <code>User</code> 以外のクラスからも使われる可能性があります。これらを使うクラス間で挙動を統一できるよう、クラス定義に手を加えるなら <code>User</code> よりも <code>UserId</code> や <code>UserName</code> に加えたいです。</p> <h2 id="結論"><a href="#%E7%B5%90%E8%AB%96">結論</a></h2> <p>次のように、 <code>UserId</code> や <code>UserName</code> にアノテーション <code>@JsonCreator</code> と <code>@JsonValue</code> をつけると実現できます。</p> <pre><code class="kt">data class User(val userId: UserId, val userName: UserName) data class UserId @JsonCreator(mode = JsonCreator.Mode.DELEGATING) constructor(@JsonValue val value: Int) data class UserName @JsonCreator(mode = JsonCreator.Mode.DELEGATING) constructor(@JsonValue val value: String) </code></pre> <h2 id="解説"><a href="#%E8%A7%A3%E8%AA%AC">解説</a></h2> <p>まず、 <a target="_blank" rel="nofollow noopener" href="http://fasterxml.github.io/jackson-annotations/javadoc/2.9/com/fasterxml/jackson/annotation/JsonValue.html">@JsonValue</a> はシリアライズに影響を与えるアノテーションで、シリアライズ結果を取得するメソッドに付けます。</p> <p>次に、 <a target="_blank" rel="nofollow noopener" href="http://fasterxml.github.io/jackson-annotations/javadoc/2.9/com/fasterxml/jackson/annotation/JsonCreator.html">@JsonCreator</a> はデシリアライズに影響を与えるアノテーションで、JSONの値からオブジェクトを作るためのコンストラクターやファクトリーメソッドに付けます。Javaの場合はコンストラクターに <code>@JsonCreator</code> をつけるだけでいいのですが、Kotlin Moduleを使っている場合はうまく行かないので、 <code>mode = JsonCreator.Mode.DELEGATING</code> が必要です [1]。</p> <p>なお、Kotlinでプライマリコンストラクターにアノテーションをつけるには、 <code>constructor</code> キーワードが必要です [2]。</p> <h2 id="コード"><a href="#%E3%82%B3%E3%83%BC%E3%83%89">コード</a></h2> <p>実際のコードは次のようになります。</p> <p>build.gradleの一部</p> <pre><code class="gradle">dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" implementation "com.fasterxml.jackson.core:jackson-databind:2.9.10.4" implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.9.10" } </code></pre> <p>Main.kt</p> <pre><code class="kt">import com.fasterxml.jackson.annotation.JsonCreator import com.fasterxml.jackson.annotation.JsonValue import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue fun main() { val u = User(UserId(1), UserName("sato")) println(u) val mapper = jacksonObjectMapper() val json = mapper.writeValueAsString(u) println(json) val parsed: User = mapper.readValue(json) println(parsed) } data class User(val userId: UserId, val userName: UserName) data class UserId @JsonCreator(mode = JsonCreator.Mode.DELEGATING) constructor(@JsonValue val value: Int) data class UserName @JsonCreator(mode = JsonCreator.Mode.DELEGATING) constructor(@JsonValue val value: String) </code></pre> <p>実行結果</p> <pre><code>User(userId=UserId(value=1), userName=UserName(value=sato)) {"userId":1,"userName":"sato"} User(userId=UserId(value=1), userName=UserName(value=sato)) </code></pre> <h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2> <ul> <li>java - How to instruct Jackson to serialize a field inside an Object instead of the Object it self? - Stack Overflow<br /> <a target="_blank" rel="nofollow noopener" href="https://stackoverflow.com/questions/11031110/how-to-instruct-jackson-to-serialize-a-field-inside-an-object-instead-of-the-object-it-self">https://stackoverflow.com/questions/11031110/how-to-instruct-jackson-to-serialize-a-field-inside-an-object-instead-of-the-object-it-self</a></li> <li>java - Is there a generic way to deserialize single-valued value objects (without custom Deserializer) in Jackson - Stack Overflow<br /> <a target="_blank" rel="nofollow noopener" href="https://stackoverflow.com/questions/55492657/is-there-a-generic-way-to-deserialize-single-valued-value-objects-without-custo">https://stackoverflow.com/questions/55492657/is-there-a-generic-way-to-deserialize-single-valued-value-objects-without-custo</a></li> </ul> <h2 id="注釈"><a href="#%E6%B3%A8%E9%87%88">注釈</a></h2> <ul> <li>[1] @jsonCreator on primary constructor is not working · Issue #305 · FasterXML/jackson-module-kotlin<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/FasterXML/jackson-module-kotlin/issues/305">https://github.com/FasterXML/jackson-module-kotlin/issues/305</a></li> <li>[2] https://kotlinlang.org/docs/reference/annotations.html#usage</li> </ul> かと tag:crieit.net,2005:PublicArticle/15162 2019-06-24T16:00:38+09:00 2019-06-24T16:00:38+09:00 https://crieit.net/posts/12d48901a56d3b6bf8c88a8f473e8138 八丈島のホテルで、運用費用0円の伝票システムアプリ作って、業務改善した話。 <p>QIitaと同じ投稿です!<br /> Qiitaで読みたいという方は<a target="_blank" rel="nofollow noopener" href="https://qiita.com/Yoji0806/items/d895d1fbd2d8c1736449">こちら</a></p> <p>このたび東京都八丈島のホテル、<a target="_blank" rel="nofollow noopener" href="http://www.lidohotels.jp/park/">リードパーク&リゾート八丈島</a>で伝票システムアプリを作らせてもらいました!</p> <p>このシステムアプリの全機能はブログの記事で動画を交えて説明しています。<br /> (QiitaじゃTwitter経由でしかアップできないため)</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.yoji0806.com/entry/2019/05/27/%E6%A5%AD%E5%8B%99%E7%94%A8%E3%83%BB%E4%BC%9D%E7%A5%A8%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%82%A2%E3%83%97%E3%83%AA%E3%81%AE%E8%AA%AC%E6%98%8E">全機能説明ページはこちら</a></p> <h1 id="謝辞"><a href="#%E8%AC%9D%E8%BE%9E">謝辞</a></h1> <h3 id="ホテルの皆様"><a href="#%E3%83%9B%E3%83%86%E3%83%AB%E3%81%AE%E7%9A%86%E6%A7%98">ホテルの皆様</a></h3> <p>今回自分がこんな貴重な機会を得られ、最後まで作ることができたのは、寛容で柔軟なホテル支配人・レストランリーダー・スタッフの皆様のお陰だと本当に思います。</p> <p>最初は遅延もあったり、レシート2枚出てきたりしてましたが、毎日使用後に多くのフィードバックを得られたので開発がとても捗りました。<br /> 「楽しい!」「今までで1番使いやすい」などの声は本当に嬉しいです:laughing:</p> <h3 id="Firebase(Google)さん"><a href="#Firebase%28Google%29%E3%81%95%E3%82%93">Firebase(Google)さん</a></h3> <p>ありがとうFirebaseとGoogleさん!!!<br /> お陰でサーバーレス・運用費0円で開発できました。<br /> 今度 Pixel 3a 買います</p> <h1 id="作ったもの  ---動画---"><a href="#%E4%BD%9C%E3%81%A3%E3%81%9F%E3%82%82%E3%81%AE%E3%80%80%E3%80%80---%E5%8B%95%E7%94%BB---">作ったもの  ---動画---</a></h1> <blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">投稿用 注文① <a target="_blank" rel="nofollow noopener" href="https://t.co/diXsgs4DQX">pic.twitter.com/diXsgs4DQX</a></p>— 山本 燿司 (@baske0806) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/baske0806/status/1133554495332311040?ref_src=twsrc%5Etfw">2019年5月29日</a></blockquote> <h1 id="作ったもの  ---図---"><a href="#%E4%BD%9C%E3%81%A3%E3%81%9F%E3%82%82%E3%81%AE%E3%80%80%E3%80%80---%E5%9B%B3---">作ったもの  ---図---</a></h1> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266096/9e6d23b5-830b-8899-2caf-8f8499dd729b.png" alt="注文受付^B伝票アプリ.png" /><br /> 👆この拙い図で表しきれてませんが、僕が作った業務アプリは、</p> <ul> <li>スタッフが客席で注文をタブレットで入力 → <strong>DBに記入</strong></li> <li>バーカウンターの端末(Fire HD10)がDBの変更を<strong>瞬時にリッスン</strong>、DBからデータを取って、Bluetooth経由でプリンターに送信 → <strong>レシート印刷</strong></li> </ul> <p>というものです。<br /> これを<strong>Android</strong> 上で<strong>Kotlin</strong> & <strong>Firebase Realtime Database</strong>でつくりました。<br /> (技術や機械の選定理由は後ほど)</p> <h1 id="ちょっと自己紹介"><a href="#%E3%81%A1%E3%82%87%E3%81%A3%E3%81%A8%E8%87%AA%E5%B7%B1%E7%B4%B9%E4%BB%8B">ちょっと自己紹介</a></h1> <p>自分は1998年生まれで、神戸大学の経済学部に通っていて現在は休学中です。(戻らないと思う。)<br /> もともと文系でプログラミングとは無縁でしたが、半年ほど前に <strong>Kotlin</strong> のチュートリアル本から学び始めて今に至ります。Kotlin大好き。<br /> 次は <strong>Flutter</strong> & <strong>Firebase</strong> でなにか作って公開しようとおもいます。</p> <p>この後は東京に行ってどこかで働きたいと思ってます!<br /> (ご縁があればよろしくお願いします:raising_hand_tone1:)</p> <h1 id="このアプリを作ることになった経緯"><a href="#%E3%81%93%E3%81%AE%E3%82%A2%E3%83%97%E3%83%AA%E3%82%92%E4%BD%9C%E3%82%8B%E3%81%93%E3%81%A8%E3%81%AB%E3%81%AA%E3%81%A3%E3%81%9F%E7%B5%8C%E7%B7%AF">このアプリを作ることになった経緯</a></h1> <p>もともとこのホテルのレストランで住み込みバイトで働いていて、空いた時間に個人開発でアプリを作ろうと思ってました。</p> <blockquote> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266096/9814216d-f413-85e5-5c63-c01282f3a94d.jpeg" alt="r01.jpg" /><br /> ホテルのレストラン: <a target="_blank" rel="nofollow noopener" href="http://www.lidohotels.jp/park/">公式ホームページ</a>より</p> </blockquote> <p>このホテルは最大200名様分の客席があり、レストランは</p> <ul> <li>全149席</li> <li>個室宴会場(40席) ある大きいレストランです。</li> </ul> <p>ここで働いているうちに色々な改善点があるなと思うようになってきました。<br /> その内の一つ(かつ最大)が、 <strong>ドリンクの注文 → 席に運ぶ</strong> までの流れと、<strong>伝票計算の処理</strong>です。</p> <p>レストランの業務は開店・閉店作業を除くと主に、以下の作業です。</p> <ol> <li>お客様を席まで案内 → 最初のドリンク注文をとる</li> </ol> <p>* 各お客様のペースに合わせて食事配膳(和食のコース料理で、約7種類)&空いた皿を下げる<br /> * ドリンクの追加注文を受けるたび、作業中断でドリンクを作り、持っていく。</p> <p>この①、③のドリンク作業がネックでした。</p> <p>従来はドリンクの注文の度、<br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266096/3839c468-744b-5ce6-39e3-2e47542180e7.png" alt="従来のホテルレストラン図.png" /></p> <p>このように、<strong>1人のスタッフが一連の作業を全て</strong>行っていました。<br /> 夕食の時間帯は3つあって(<strong>18時~/18時30分~/19時~</strong>)、それぞれの時間帯で<strong>約20~50名</strong>ほどが一気に来られます。スタッフは1席ごとの注文で1往復するので、最初のドリンクを早く捌かないとその後の全ての作業が遅れてしまいます。<br /> 追加注文の際も、作業が中断されるので効率が落ちてしまいます。</p> <p>そしてお客が全員帰ると、<strong>各伝票に各ドリンクの単価を記入</strong>して、<strong>電卓で合計を計算</strong>、記入していました。(多いときは100人分ほど)</p> <p>この作業をなくしたいと思い、試作品を作って、支配人・ホテルリーダーに見せると即決で「試してみよう」となり、開発が決まりました!</p> <h1 id="導入後の作業フロー"><a href="#%E5%B0%8E%E5%85%A5%E5%BE%8C%E3%81%AE%E4%BD%9C%E6%A5%AD%E3%83%95%E3%83%AD%E3%83%BC">導入後の作業フロー</a></h1> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266096/99ee545f-40db-2a45-9409-1ee21fcd2a5d.png" alt="新しいホテルレストラン図.png" /></p> <p>各スタッフが端末(Fire 7)をポケットに入れ、バーカウンターに通信用の端末1台と、サーマルプリンター1台を置いています。</p> <p><code>注文を受けるスタッフ</code>は、バーカウンターに行く必要はなく、注文入力後、次の席に行ってまた注文を受けられます。<br /> <code>バーカウンターのスタッフ</code>は次々と印刷されるレシートを見て、ドリンクを作り、お盆にレシートと一緒にドリンクを置いていきます。<br /> そして<code>手の空いているスタッフ</code>が、そのお盆を持ち、レシートに記されている席まで運びます。(レシートはどんどん重ねて挟んでいく。)</p> <p>このように、<strong>作業の分業化</strong>ができるようになりました!!<br /> さらに<strong>単価もレシートに記載されていおり、最後に1枚1枚伝票を計算する必要もなくなりました</strong>!</p> <h1 id="使用した技術・機器の選定理由"><a href="#%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%9F%E6%8A%80%E8%A1%93%E3%83%BB%E6%A9%9F%E5%99%A8%E3%81%AE%E9%81%B8%E5%AE%9A%E7%90%86%E7%94%B1">使用した技術・機器の選定理由</a></h1> <h2 id="ハード面"><a href="#%E3%83%8F%E3%83%BC%E3%83%89%E9%9D%A2">ハード面</a></h2> <h3 id="Why Fire7 ?"><a href="#Why+Fire7+%EF%BC%9F">Why Fire7 ?</a></h3> <p>自分がここのホテルに来る前、他の伝票サービスを導入する試みがあったらしく、もともとFire 7 はありました。(結局導入には至らなかったそうです。)<br /> それに、ホテルのソムリエエプロンのポッケに丁度フィットします:v:</p> <h3 id="Why(サーマルプリンター) StarPrnt TSP650||?"><a href="#Why%EF%BC%88%E3%82%B5%E3%83%BC%E3%83%9E%E3%83%AB%E3%83%97%E3%83%AA%E3%83%B3%E3%82%BF%E3%83%BC%EF%BC%89+StarPrnt+TSP650%7C%7C%EF%BC%9F">Why(サーマルプリンター) StarPrnt TSP650||?</a></h3> <p>スター精密は安心できるし、SDKもしっかりしていて、尚且つ安価なのが決めてでした。(4万円弱)<br /> このモデルはBluetooth通信に対応していて、印刷速度も1番早かったのでこれに決めました。</p> <h2 id="ソフト面"><a href="#%E3%82%BD%E3%83%95%E3%83%88%E9%9D%A2">ソフト面</a></h2> <h3 id="Why Android?"><a href="#Why+Android%EF%BC%9F">Why Android?</a></h3> <p>もともとFire端末があり、自分もAndridを学び始めたばっかだったから。<br /> (Fire OSは、独自といってますがほぼAndroid。)<br /> この偶然はラッキーでした。</p> <h3 id="Why Kotlin?"><a href="#Why+Kotlin%3F">Why Kotlin?</a></h3> <p>自分が学び始めたのがKotlinからだったから。<br /> (Javaと100%相互互換なので結果的にJavaも段々わかってきた。)</p> <h3 id="Why Firebase?"><a href="#Why+Firebase%EF%BC%9F">Why Firebase?</a></h3> <p>最初はAWSかAzureと思っていたのですが、Firebaseは無料枠(しかも大容量)があり、それにDBの性能が最高でこれだけで十分だったからです。(とくにリアルタイム同期とオフライン処理)<br /> Firebaseの無料枠が、AWS(Amazon)・Azure(Microsoft)との競争の中で勝つための策だとしたらAWS, Azureにも感謝です。</p> <p>👇100名ほどのディナー1日で、この容量なので、まだまだまだ余裕があります。<br /> <img width="702" alt="2019-05-29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266096/2c20edbe-ad41-01eb-eda1-3bf365e97110.png"></p> <h3 id="Why Firebase RealtimeDB?"><a href="#Why+Firebase+RealtimeDB%EF%BC%9F">Why Firebase RealtimeDB?</a></h3> <p>これは、FireStoreと悩んだのですが結局RealtimeDBにしました。理由は、</p> <ul> <li>Realtime DBは Key-Valueなので、高速。</li> <li>FireStoreの特徴の強力なクエリは必要なかった。</li> <li><del>ベータ版なのでちょっと気が引ける。</del></li> </ul> <p>でしたが、よく考えると お客130人前後の注文情報なんて<strong>500B</strong>ぐらいで済むので、遅延とかはそこまで変わらないと思います。</p> <p>それに最近知ったのですが、<strong>FireStoreの方はGoogleのインフラをフル活用</strong>していて、今後GoogleはFirestore推しでいくらしいです。<br /> <del>ですが、β版というのが気になりました。</del></p> <p>次の個人開発からはFirestoreを使います。</p> <p><code>※追記</code><br /> @chronicle さんのご指摘で、見落としに気付きました。ありがとうございます!<br /> <strong>Firestoreは現在正規版で、GCPのサービス品質保証契約(SLA)も適用されてます! どんどん使いましょう!</strong></p> <h1 id="課題解決"><a href="#%E8%AA%B2%E9%A1%8C%E8%A7%A3%E6%B1%BA">課題解決</a></h1> <p>ここからは、開発の過程でつまづいた所、その解決策を書いていきます。</p> <h2 id="レシート1枚で全ての情報を表す。"><a href="#%E3%83%AC%E3%82%B7%E3%83%BC%E3%83%88%EF%BC%91%E6%9E%9A%E3%81%A7%E5%85%A8%E3%81%A6%E3%81%AE%E6%83%85%E5%A0%B1%E3%82%92%E8%A1%A8%E3%81%99%E3%80%82">レシート1枚で全ての情報を表す。</a></h2> <p>分業化するにあたって、<code>注文を受ける人</code>・<code>ドリンクを作る人</code>・<code>運ぶ人</code>はレシート1枚を通して情報伝達を行う必要があります。必要な情報は、<br /> <strong>ドリンクを作るのに</strong></p> <ul> <li>ドリンク名(もしくは商品名)</li> <li>その飲み方(お湯割りやロックなど)</li> <li>あればオプション(「常温で」、「レモンつけて」など)</li> </ul> <p><strong>テーブルまで運ぶのに</strong></p> <ul> <li>テーブル番号</li> </ul> <p><strong>会計用に</strong></p> <ul> <li>そのテーブルの全注文履歴と合計金額</li> <li>各ドリンク(商品)の単価</li> <li>お部屋番号(会計は部屋付けなので)</li> </ul> <p>以上の情報を1枚のレシートに入れなければなりません。<br /> 最初はStarPrntのSDKを使えば、なんか良い感じに自動で割り振ってくれると思ってました(希望的観測)。</p> <p><strong>が、実際は自分でテンプレートを作ったり、行数・フォントサイズを調整</strong>する必要がありました。</p> <p>これはこれでめっちゃ楽しかったです。まず普段のレシートに目が行くようになって、「あ~これ手抜いてるな」とか「すげぇ!どうやって!?」みたいに思うところが増えました。</p> <p>コードを貼るとめちゃ長くなるので、別記事でレシートの作り方はまとめます。</p> <blockquote> <p><code>※追記※書きました!</code><br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/Yoji0806/items/fdb45341b0c1a55ae94d">【誰得】普段目にする、レシート印刷の実装方法</a></p> </blockquote> <p>少し言うと、縦横を揃えるためにすべての文字を全角にして、計算して空白を適切な数入れたり、1行に収められるようにしたりしました。👇<br /> (半角のサイズは全角の半分でないため、混ざっているとややこしい。)</p> <pre><code class="kotlin"><br /> var order = Transliterator.getInstance("Halfwidth-Fullwidth").transliterate(orderList[x]) if (order.count() >= 18){ order = order.substring(0, 18) } val size = order.count() val rest = 22 - size data = (order + " ".repeat(rest - 3) + qList[x] + "\n") </code></pre> <p>超はしょって、試行錯誤の末こうすることで解決しました。<br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266096/b28948be-aef6-ad84-921f-f1225ed4360e.jpeg" alt="レシートの.jpg" /></p> <p>以下の点に気を付けました。</p> <ul> <li>お客様にとって重要な情報は<strong>大きく太く</strong>(ここはお客様の年齢層も高く、見やすいレイアウトを心がけた)</li> <li>お客にとって重要でない情報(日時、作り方、端末番号)は、小さく明記</li> <li><strong>注文ごとをブロックで分け</strong>、どの注文を作れば良いのか、わかるようにした。(常に1番下のブロックのドリンクを作る)</li> </ul> <p>これで、<code>お客</code>は何を頼んで合計は何円かわかるし、<code>ドリンクを作る人</code>も何をどのように作るのか、<code>運ぶ人</code>もどこに運ぶのかを一目でわかるようになりました!</p> <p>注文ごとをブロックで分けるには<strong>DBの設計</strong>を見直す必要がありました。<br /> DB設計で気をつけたことは後述しますが、これは<strong>柔軟なNoSQLモデル</strong>だからこそ簡単にできたのだと思います。</p> <h2 id="プリンターの排他制御"><a href="#%E3%83%97%E3%83%AA%E3%83%B3%E3%82%BF%E3%83%BC%E3%81%AE%E6%8E%92%E4%BB%96%E5%88%B6%E5%BE%A1">プリンターの排他制御</a></h2> <p>これはめちゃくちゃ悩みました。まず、排他制御自体を知らなかったので探そうにもGoogleは教えてくれず、、、だったのですが<a target="_blank" rel="nofollow noopener" href="https://menta.work/">MENTA</a>というサービスでメンターの方に相談するとすぐに解決しました。</p> <p>Kotlin(Java)ではとっても簡単で、</p> <pre><code class="kotlin">@Syncronized fun a (){ //処理 } </code></pre> <p>👆このように関数の上に <code>@Syncronized</code>を入力するだけで排他制御が実装できます。</p> <p>あとはコードの設計を見直して、排他制御を実装した関数内で<br /> <strong>DBからデータの読み取り → レシート情報作り → 印刷 → 初期化</strong><br /> の処理を行うようにします。<br /> 13台同時に注文してもきちんと印刷されました:relaxed:</p> <h2 id="DBの設計"><a href="#DB%E3%81%AE%E8%A8%AD%E8%A8%88">DBの設計</a></h2> <p>Firebase RealtimeDBでは、リレイショナルなモデルではなく、データをJSONツリー型で保持するNoSQLモデルです。</p> <p>自分はリレイショナルなDBをほとんど触ったことがなかったので、逆に変な違和感とかはなかったです。<br /> このモデルで大事なことは2つで、</p> <ul> <li>余計なデータまで読み込まないように、<strong>深くネストしない</strong></li> <li>効率よく必要なデータを見つけるための、<strong>平坦化(非正規化)</strong></li> </ul> <p>です。さらにこのアプリでは、DB設計を考えるにあたって</p> <ul> <li><strong>各注文ごとをブロックで分ける</strong>(理由は上記)</li> <li>簡単にレシート印刷できるようにする</li> </ul> <p>という事を考えながら設計し、以下のようにしました👇!<br /> (11番テーブルの注文のみ)</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266096/36efacc4-a204-4ee6-5c42-6e6bd829f6c5.png" alt="db設計.png" /></p> <p>RealtimeDBでは、最大34回ネストできるのですが、ここでは5回に抑えることができました。</p> <p>そしてどのデータもテーブル番号 (<strong>T</strong>able <strong>N</strong>umber) (画像では <strong>TN 11</strong>)と紐づけることで、注文2回目以降は、テーブル番号入力後、すぐにメニュー画面へ遷移でき、途中でデータの削除や数量変更などもできます。</p> <p>また、一番上の<strong>"Checker"ノード</strong>を作ることで、、効率的にレシートを印刷できます。<br /> 色々なクラスからレシートを印刷する必要があるのですが(注文履歴変更後や、確認用など)、それも以下のコードで実装できます。👇</p> <pre><code class="kotlin"><br />val table = "11" //実際のコードでは、この値をintentで渡している。 //実際には、トランザクション処理 val mDatabase = Firebase.getInstance().getReference("Checker") mDatabase.child("TN $table").child("check").setValue(false) mDatabase.child("TN $table").child("check").setValue(true) </code></pre> <p>それに、プリンター横の端末は、<strong>"Checker"ノードにリスナーを設置し、</strong>"check"の値がfalse → trueになるのを監視するだけでいいので、こうしています👇</p> <pre><code class="kotlin"> val check: Boolean = p0.child("check").value.toString().toBoolean() val tablet = p0.child("tablet").value.toString() if (!oldValue && check){ //oldValueはリスナー外で定義している val tableNum = p0.key.toString() readData(tableNum, tablet) //テーブルの情報を全部読み取り、レシート印刷 } oldValue = check </code></pre> <p>時々レシートが2枚印刷される問題は、この実装で解決しました。</p> <h2 id="直感的なUI"><a href="#%E7%9B%B4%E6%84%9F%E7%9A%84%E3%81%AAUI">直感的なUI</a></h2> <p>前に導入しようとしていた伝票システムは、全体的に単色で、メニューの選択画面が文字のリストで、とても見にくかったとのことでした。</p> <p>なので自分は<strong>極力文字を少なく・画像やベクター図を多く、見やすい色やフォントサイズ</strong>を心がけました。<br /> また、1つの画面で全ての操作を終えるのはなく、<strong>各画面で行う操作は1つ</strong>にしました。</p> <p>スタッフは画面毎の<strong>単純な質問に答えるように</strong>操作していきます。</p> <p>(例えば、日本酒を選択すると<code>おちょこの数</code>を質問し、ワインだと<code>グラスの数</code>、焼酎だと<code>飲み方</code>、ソフトドリンクだと、<code>氷の有無</code>や<code>アイスorホット</code>を質問します。)</p> <p>👇下は、焼酎のボトルを注文するときの画面遷移<br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266096/80c07339-693f-7562-0b36-baa68e724f74.jpeg" alt="UI一覧.jpg" /></p> <p><strong>流れるように素早い操作</strong>が可能なように心がけましたが、そうすると一方でミスが増える可能性がでてきます。<br /> なので、<strong>できるだけミスを無くすため</strong>に、また、お客様の<strong>急な変更に対応</strong>できるように(「やっぱビール3つで!」みたいな)、<br /> 注文が3種類以上の時は自動的に<code>【注文内容は大丈夫?】</code>画面に遷移するようにしました!<br /> ここの画面から、商品の追加や数量変更・削除も可能です。</p> <blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">Qiita投稿用 <a target="_blank" rel="nofollow noopener" href="https://t.co/Dx0tktsMii">pic.twitter.com/Dx0tktsMii</a></p>— 山本 燿司 (@baske0806) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/baske0806/status/1133707670618136577?ref_src=twsrc%5Etfw">2019年5月29日</a></blockquote> <h2 id="コツコツ改善 ①SoftKeyboardのフォーカス調節"><a href="#%E3%82%B3%E3%83%84%E3%82%B3%E3%83%84%E6%94%B9%E5%96%84+%E2%91%A0SoftKeyboard%E3%81%AE%E3%83%95%E3%82%A9%E3%83%BC%E3%82%AB%E3%82%B9%E8%AA%BF%E7%AF%80">コツコツ改善 ①SoftKeyboardのフォーカス調節</a></h2> <p>あとは、小さなことなんですが、注文の最後の画面では、EditTextのフォーカスをデフォルトで外しているのですが、<code>メモを残す</code>ボタンタップ時にソフトキーボードがでてくるようにしました!</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266096/6075553d-1fd6-a755-2b9b-910c9f088ec9.png" alt="キーボード.png" /></p> <p>このコード👇で、ソフトキーボードを出す実装ができます。</p> <pre><code class="kotlin">fun showSoftKeyboard(view:View) { if (view.requestFocus()) { val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT) } } </code></pre> <p><a target="_blank" rel="nofollow noopener" href="http://www.androidtutorialshub.com/how-to-hide-and-show-soft-keyboard-in-android/">:参考ページ</a> 【How to Hide and Show Soft Keyboard in Android】</p> <h2 id="コツコツ改善 ②ホーム画面整理"><a href="#%E3%82%B3%E3%83%84%E3%82%B3%E3%83%84%E6%94%B9%E5%96%84+%E2%91%A1%E3%83%9B%E3%83%BC%E3%83%A0%E7%94%BB%E9%9D%A2%E6%95%B4%E7%90%86">コツコツ改善 ②ホーム画面整理</a></h2> <p>これは、システム関係ないのですが、もし誤操作でホーム画面に戻ってしまっても、一目でどれが伝票システムアプリかわかるようにしました。(完全人力)<br /> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266096/90c9ead9-7282-abe4-f660-3e52f35f8b7e.jpeg" alt="ホーム画面.jpg" /></p> <h1 id="終わりに"><a href="#%E7%B5%82%E3%82%8F%E3%82%8A%E3%81%AB">終わりに</a></h1> <p>ハード機器の選定から、DB設計、UIまで、全部自分1人でやると色々わかってきますが、1番思うことは、自分のレベルの低さです。(本当に)</p> <p>そして、<strong>フィードバックの大切さ</strong>。個人開発のアプリでは今回のような直接的なフィードバックがない分、色々なLogや、ユーザーのデータが相当すると思うので、次に活かしたいです。</p> <p>あとは<strong>メンターの大切さ</strong>。今回自分は登録だけして使いませんでしたが、英語版では<a target="_blank" rel="nofollow noopener" href="https://www.codementor.io/">CodeMentor</a>、日本語版では<a target="_blank" rel="nofollow noopener" href="https://menta.work/">MENTA</a>などがあるので、積極的に利用していきたいです。</p> <p>最後まで読んで頂いてとても嬉しいです!<br /> ありがとうございました!</p> 山本 燿司