20190529のAndroidに関する記事は7件です。

【Unity】UnityでAndroidアプリ64bit対応する方法

環境メモ
⭐️Mac OS Mojave バージョン10.14
⭐️Unity 2018.2.15f1

Google Playより以下メールが届いたので、対応する

[ご対応をお願いいたします]: 2019 年 8 月 1 日以降、アプリの 64 ビット版をご用意ください
2019 年 8 月 1 日より、ネイティブ コードを使用するすべてのアプリにおいて、アップデートを公開するには 32 ビット版に加えて 64 ビット版を提供することが必須となります。本年 1 月にもお知らせいたしましたとおり、今後は 64 ビット用コードのみをサポートする Android デバイスへの移行が進むと予想されており、今回の 64 ビット必須化はこうしたイノベーションに対応するための措置となります。 弊社にて状況を確認しましたところ、お客様のアプリで 64 ビット版がまだ提供されていないものが見つかりました。期限が近づいておりますので、ご確認のうえご対応をお願いいたします。

UnityでAndroidアプリ64bit対応する

1.「Unity」ー「Preference」を選択する
スクリーンショット 2019-05-29 21.26.00.png

2.「External Tools」NDK箇所のダウンロードボタンを押し、NDKをダウンロードする
スクリーンショット 2019-05-29 22.53.37.png

3.ダウンロードしたNDKファイルを解凍し(普通に、ダブルクリックで解答できました)
NDK箇所に、解答したフォルダを指定する
スクリーンショット 2019-05-29 22.56.18.png

4.「Eidt」ー「Project Settings」ー「Player」を開く
スクリーンショット 2019-05-29 22.58.31.png

5.「Configuration」の
Scriptiong Backendを「IL2CPP」を選択し、
ARM64をチェックONする
スクリーンショット 2019-05-29 21.25.34.png

これで、ビルドして、APKを作成すればOK。完了です。



↓↓Google Play 無料Free
VR ピヨくまちゃんのシューティング〜VR Shooting Piyo-Kuma-Chan
vrpiyokumachan.png

↓↓Google Play 無料Free
CherryCocktailGlassチェリーカクテルグラス〜無料簡単ミニゲームFree games
googleStoreLink.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

八丈島のホテルで、運用費用0円の伝票システムアプリ作って、業務改善した話。

はじめまして!
いつも皆さんの面白い記事を読んで、学んだり励みにしたり楽しんだりしてます!ありがとうございます!

このたび東京都八丈島のホテル、リードパーク&リゾート八丈島で伝票システムアプリを作らせてもらいました!

このシステムアプリの全機能はブログの記事で動画を交えて説明しています。
(QiitaじゃTwitter経由でしかアップできないため)

全機能説明ページはこちら

謝辞

ホテルの皆様

今回自分がこんな貴重な機会を得られ、最後まで作ることができたのは、寛容で柔軟なホテル支配人・レストランリーダー・スタッフの皆様のお陰だと本当に思います。

最初は遅延もあったり、レシート2枚出てきたりしてましたが、毎日使用後に多くのフィードバックを得られたので開発がとても捗りました。
「楽しい!」「今までで1番使いやすい」などの声は本当に嬉しいです:laughing:

Firebase(Google)さん

ありがとうFirebaseとGoogleさん!!!
お陰でサーバーレス・運用費0円で開発できました。
今度 Pixel 3a 買います

作ったもの  ---動画---

作ったもの  ---図---

注文受付^B伝票アプリ.png
?この拙い図で表しきれてませんが、僕が作った業務アプリは、

  • スタッフが客席で注文をタブレットで入力 → DBに記入
  • バーカウンターの端末(Fire HD10)がDBの変更を瞬時にリッスン、DBからデータを取って、Bluetooth経由でプリンターに送信 → レシート印刷

というものです。
これをAndroid 上でKotlin & Firebase Realtime Databaseでつくりました。
(技術や機械の選定理由は後ほど)

ちょっと自己紹介

自分は1998年生まれで、神戸大学の経済学部に通っていて現在は休学中です。(戻らないと思う。)
もともと文系でプログラミングとは無縁でしたが、半年ほど前に Kotlin のチュートリアル本から学び始めて今に至ります。Kotlin大好き。
次は Flutter & Firebase でなにか作って公開しようとおもいます。

この後は東京に行ってどこかで働きたいと思ってます!
(ご縁があればよろしくお願いします:raising_hand_tone1:

このアプリを作ることになった経緯

もともとこのホテルのレストランで住み込みバイトで働いていて、空いた時間に個人開発でアプリを作ろうと思ってました。

r01.jpg
ホテルのレストラン: 公式ホームページより

このホテルは最大200名様分の客席があり、レストランは

  • 全149席
  • 個室宴会場(40席) ある大きいレストランです。

ここで働いているうちに色々な改善点があるなと思うようになってきました。
その内の一つ(かつ最大)が、 ドリンクの注文 → 席に運ぶ までの流れと、伝票計算の処理です。

レストランの業務は開店・閉店作業を除くと主に、以下の作業です。

  1. お客様を席まで案内 → 最初のドリンク注文をとる
  2. 各お客様のペースに合わせて食事配膳(和食のコース料理で、約7種類)&空いた皿を下げる
  3. ドリンクの追加注文を受けるたび、作業中断でドリンクを作り、持っていく。

この①、③のドリンク作業がネックでした。

従来はドリンクの注文の度、
従来のホテルレストラン図.png

このように、1人のスタッフが一連の作業を全て行っていました。
夕食の時間帯は3つあって(18時~/18時30分~/19時~)、それぞれの時間帯で約20~50名ほどが一気に来られます。スタッフは1席ごとの注文で1往復するので、最初のドリンクを早く捌かないとその後の全ての作業が遅れてしまいます。
追加注文の際も、作業が中断されるので効率が落ちてしまいます。

そしてお客が全員帰ると、各伝票に各ドリンクの単価を記入して、電卓で合計を計算、記入していました。(多いときは100人分ほど)

この作業をなくしたいと思い、試作品を作って、支配人・ホテルリーダーに見せると即決で「試してみよう」となり、開発が決まりました!

導入後の作業フロー

新しいホテルレストラン図.png

各スタッフが端末(Fire 7)をポケットに入れ、バーカウンターに通信用の端末1台と、サーマルプリンター1台を置いています。

注文を受けるスタッフは、バーカウンターに行く必要はなく、注文入力後、次の席に行ってまた注文を受けられます。
バーカウンターのスタッフは次々と印刷されるレシートを見て、ドリンクを作り、お盆にレシートと一緒にドリンクを置いていきます。
そして手の空いているスタッフが、そのお盆を持ち、レシートに記されている席まで運びます。(レシートはどんどん重ねて挟んでいく。)

このように、作業の分業化ができるようになりました!!
さらに単価もレシートに記載されていおり、最後に1枚1枚伝票を計算する必要もなくなりました

使用した技術・機器の選定理由

ハード面

Why Fire7 ?

自分がここのホテルに来る前、他の伝票サービスを導入する試みがあったらしく、もともとFire 7 はありました。(結局導入には至らなかったそうです。)
それに、ホテルのソムリエエプロンのポッケに丁度フィットします:v:

Why(サーマルプリンター) StarPrnt TSP650||?

スター精密は安心できるし、SDKもしっかりしていて、尚且つ安価なのが決めてでした。(4万円弱)
このモデルはBluetooth通信に対応していて、印刷速度も1番早かったのでこれに決めました。

ソフト面

Why Android?

もともとFire端末があり、自分もAndridを学び始めたばっかだったから。
(Fire OSは、独自といってますがほぼAndroid。)
この偶然はラッキーでした。

Why Kotlin?

自分が学び始めたのがKotlinからだったから。
(Javaと100%相互互換なので結果的にJavaも段々わかってきた。)

Why Firebase?

最初はAWSかAzureと思っていたのですが、Firebaseは無料枠(しかも大容量)があり、それにDBの性能が最高でこれだけで十分だったからです。(とくにリアルタイム同期とオフライン処理)
Firebaseの無料枠が、AWS(Amazon)・Azure(Microsoft)との競争の中で勝つための策だとしたらAWS, Azureにも感謝です。

?100名ほどのディナー1日で、この容量なので、まだまだまだ余裕があります。
2019-05-29.png

Why Firebase RealtimeDB?

これは、FireStoreと悩んだのですが結局RealtimeDBにしました。理由は、

  • Realtime DBは Key-Valueなので、高速。
  • FireStoreの特徴の強力なクエリは必要なかった。
  • ベータ版なのでちょっと気が引ける。

でしたが、よく考えると お客130人前後の注文情報なんて500Bぐらいで済むので、遅延とかはそこまで変わらないと思います。

それに最近知ったのですが、FireStoreの方はGoogleのインフラをフル活用していて、今後GoogleはFirestore推しでいくらしいです。
ですが、β版というのが気になりました。

次の個人開発からはFirestoreを使います。

※追記
@chronicle さんのご指摘で、見落としに気付きました。ありがとうございます!
Firestoreは現在正規版で、GCPのサービス品質保証契約(SLA)も適用されてます! どんどん使いましょう!

課題解決

ここからは、開発の過程でつまづいた所、その解決策を書いていきます。

レシート1枚で全ての情報を表す。

分業化するにあたって、注文を受ける人ドリンクを作る人運ぶ人はレシート1枚を通して情報伝達を行う必要があります。必要な情報は、
ドリンクを作るのに

  • ドリンク名(もしくは商品名)
  • その飲み方(お湯割りやロックなど)
  • あればオプション(「常温で」、「レモンつけて」など)

テーブルまで運ぶのに

  • テーブル番号

会計用に

  • そのテーブルの全注文履歴と合計金額
  • 各ドリンク(商品)の単価
  • お部屋番号(会計は部屋付けなので)

以上の情報を1枚のレシートに入れなければなりません。
最初はStarPrntのSDKを使えば、なんか良い感じに自動で割り振ってくれると思ってました(希望的観測)。

が、実際は自分でテンプレートを作ったり、行数・フォントサイズを調整する必要がありました。

これはこれでめっちゃ楽しかったです。まず普段のレシートに目が行くようになって、「あ~これ手抜いてるな」とか「すげぇ!どうやって!?」みたいに思うところが増えました。

コードを貼るとめちゃ長くなるので、別記事でレシートの作り方はまとめます。

少し言うと、縦横を揃えるためにすべての文字を全角にして、計算して空白を適切な数入れたり、1行に収められるようにしたりしました。?
(半角のサイズは全角の半分でないため、混ざっているとややこしい。)

   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")    

超はしょって、試行錯誤の末こうすることで解決しました。
レシートの.jpg

以下の点に気を付けました。

  • お客様にとって重要な情報は大きく太く(ここはお客様の年齢層も高く、見やすいレイアウトを心がけた)
  • お客にとって重要でない情報(日時、作り方、端末番号)は、小さく明記
  • 注文ごとをブロックで分け、どの注文を作れば良いのか、わかるようにした。(常に1番下のブロックのドリンクを作る)

これで、お客は何を頼んで合計は何円かわかるし、ドリンクを作る人も何をどのように作るのか、運ぶ人もどこに運ぶのかを一目でわかるようになりました!

注文ごとをブロックで分けるにはDBの設計を見直す必要がありました。
DB設計で気をつけたことは後述しますが、これは柔軟なNoSQLモデルだからこそ簡単にできたのだと思います。

プリンターの排他制御

これはめちゃくちゃ悩みました。まず、排他制御自体を知らなかったので探そうにもGoogleは教えてくれず、、、だったのですがMENTAというサービスでメンターの方に相談するとすぐに解決しました。

Kotlin(Java)ではとっても簡単で、

@Syncronized
fun a (){
//処理
}

?このように関数の上に @Syncronizedを入力するだけで排他制御が実装できます。

あとはコードの設計を見直して、排他制御を実装した関数内で
DBからデータの読み取り → レシート情報作り → 印刷 → 初期化
の処理を行うようにします。
13台同時に注文してもきちんと印刷されました:relaxed:

DBの設計

Firebase RealtimeDBでは、リレイショナルなモデルではなく、データをJSONツリー型で保持するNoSQLモデルです。

自分はリレイショナルなDBをほとんど触ったことがなかったので、逆に変な違和感とかはなかったです。
このモデルで大事なことは2つで、

  • 余計なデータまで読み込まないように、深くネストしない
  • 効率よく必要なデータを見つけるための、平坦化(非正規化)

です。さらにこのアプリでは、DB設計を考えるにあたって

  • 各注文ごとをブロックで分ける(理由は上記)
  • 簡単にレシート印刷できるようにする

という事を考えながら設計し、以下のようにしました?!
(11番テーブルの注文のみ)

db設計.png

RealtimeDBでは、最大34回ネストできるのですが、ここでは5回に抑えることができました。

そしてどのデータもテーブル番号 (Table Number) (画像では TN 11)と紐づけることで、注文2回目以降は、テーブル番号入力後、すぐにメニュー画面へ遷移でき、途中でデータの削除や数量変更などもできます。

また、一番上の"Checker"ノードを作ることで、、効率的にレシートを印刷できます。
色々なクラスからレシートを印刷する必要があるのですが(注文履歴変更後や、確認用など)、それも以下のコードで実装できます。?

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)

それに、プリンター横の端末は、"Checker"ノードにリスナーを設置し、"check"の値がfalse → trueになるのを監視するだけでいいので、こうしています?

 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

時々レシートが2枚印刷される問題は、この実装で解決しました。

直感的なUI

前に導入しようとしていた伝票システムは、全体的に単色で、メニューの選択画面が文字のリストで、とても見にくかったとのことでした。

なので自分は極力文字を少なく・画像やベクター図を多く、見やすい色やフォントサイズを心がけました。
また、1つの画面で全ての操作を終えるのはなく、各画面で行う操作は1つにしました。

スタッフは画面毎の単純な質問に答えるように操作していきます。

(例えば、日本酒を選択するとおちょこの数を質問し、ワインだとグラスの数、焼酎だと飲み方、ソフトドリンクだと、氷の有無アイスorホットを質問します。)

?下は、焼酎のボトルを注文するときの画面遷移
UI一覧.jpg

流れるように素早い操作が可能なように心がけましたが、そうすると一方でミスが増える可能性がでてきます。
なので、できるだけミスを無くすために、また、お客様の急な変更に対応できるように(「やっぱビール3つで!」みたいな)、
注文が3種類以上の時は自動的に【注文内容は大丈夫?】画面に遷移するようにしました!
ここの画面から、商品の追加や数量変更・削除も可能です。

コツコツ改善 ①SoftKeyboardのフォーカス調節

あとは、小さなことなんですが、注文の最後の画面では、EditTextのフォーカスをデフォルトで外しているのですが、メモを残すボタンタップ時にソフトキーボードがでてくるようにしました!

キーボード.png

このコード?で、ソフトキーボードを出す実装ができます。

fun showSoftKeyboard(view:View) {
  if (view.requestFocus())
  {
    val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
    imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT)
  }
}

:参考ページ 【How to Hide and Show Soft Keyboard in Android】

コツコツ改善 ②ホーム画面整理

これは、システム関係ないのですが、もし誤操作でホーム画面に戻ってしまっても、一目でどれが伝票システムアプリかわかるようにしました。(完全人力)
ホーム画面.jpg

終わりに

ハード機器の選定から、DB設計、UIまで、全部自分1人でやると色々わかってきますが、1番思うことは、自分のレベルの低さです。(本当に)

そして、フィードバックの大切さ。個人開発のアプリでは今回のような直接的なフィードバックがない分、色々なLogや、ユーザーのデータが相当すると思うので、次に活かしたいです。

あとはメンターの大切さ。今回自分は登録だけして使いませんでしたが、英語版ではCodeMentor、日本語版ではMENTAなどがあるので、積極的に利用していきたいです。

最後まで読んで頂いてとても嬉しいです!
ありがとうございました!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Google Playに更新ボタンが出ない問題】の解決&備忘録

【きっかけ】

すでにリリース済み(Version 1.0)のアプリをアップデートしようと思い、
通常通りGoogle Play Cosoleでapk(version 1.3)を追加し、リリース処理をした。
それが無事に通過してgoogle Play上ではversion 1.3などと表示されている。

にもかかわらず「更新」とか「アップデート」ボタンが表示されない!
つまり、既存のユーザーがアップデートできない!!!
という問題に直面したので、その原因と解決方法を記載しておきます。
原因はとても恥ずかしいものですが、この問題になかなか気づけなかった自分への反省の意を込めて記載します。

【原因】

【リリース済のapk】version 1.3のBundle Version Codeが「1」となっていて
【更新させたいapk】version 1.0のBundle Version Codeが「2」となっていた。

Google Play Console上からも確認します。
左メニューから
 リリース管理 > アプリリリース > 製品版トラック >リリース履歴
とたどると、過去のバージョンコードが表示されます。
怒るおじさんーバージョン履歴比較.png

version 1.3(バージョンコード 2) < version 1.0(バージョンコード 1)

バージョンコード(Bundle Version Code)が新しいやつの方が小さい値だった!

簡単に言うとこれが原因です。

どういう意味かを解説しますと、
Unityでは「Bundle Version Code」(Google Play Console上では「バージョンコード」と呼ばれるもの)の数値が
大きいものがgoogle ストア側で「新しい」と判断されるということなのです。
ここでいうversion 1.0とかversion1.3というのが「ストアに表示させる便宜上のバージョン名」なので、これはバージョン管理にはまったく関係ない!ってことなのです。

上記の場合のBundle Version Codeが
version 1.3 < version 1.0
となっていたためversion 1.0の方が新しいアプリと判断され、そのため「更新」ボタンが表示されなかったのです。
ただ、アプリをアンインストールして、Google Playストアからインストールするとversion 1.3になっちゃいます。
googleのサポートさんにいろいろと問い合わせしたのですが、結局は自分のBundle Version Codeのミスとたどり着きました!
そもそも、なんでversion 1.0でBundle Version Codeを2として登録したのかが意味不明ですが、
これ以上過去の自分を責めても仕方ないのでやめておきます。

【言い訳】
いや、当然と言えば当然なんですが、iOSだとversion とコードがセットで管理されてるんですよね。だからそのなんというか…

【結論】Bundle Version Code(バージョンコード)は必ず前のアプリのバージョンより新しくすること!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReactNative実行時のエラー解決メモ

react nativeを0.57.3から0.59.8に上げて実行したらエラーがたくさん出たので解決方法をメモしておきます。

android

Could not get unknown property 'mergeResourcesProvider' for object of type com.android.build.gradle.internal.api.ApplicationVariantImpl.

https://github.com/wix/react-native-navigation/issues/4757#issuecomment-468133753
上記コメント通りに対応すれば解決しました。
metro.config.jsファイルを作成するとありますが、0.59の正式リリースで必要なくなったみたいなので作らなくて大丈夫です。
また、バージョンはいきなり全て最新にすると依存関係でハマったりするので、メジャーバージョンを少しづつあげていく方がいいと思います。

Execution failed for task ':app:processDebugResources'.
 Android resource linking failed

  /Users/<USER_NAME>/.gradle/caches/transforms-1/files-1.1/appcompat-v7-28.0.0.aar/6f6de8a5350930056a96225b06ab7a16/res/values-v28/values-v28.xml:9:5-12:13: AAPT: error: resource android:attr/dialogCornerRadius not found.
  /Users/<USER_NAME>/<PROJECT_NAME>/android/app/build/intermediates/incremental/mergeDebugResources/merged.dir/values-v28/values-v28.xml:11: AAPT: error: resource android:attr/dialogCornerRadius not found.
  /Users/<USER_NAME>/.gradle/caches/transforms-1/files-1.1/drawee-1.10.0.aar/603dac14beab9d235ac515ad5b5f1fe7/res/values/values.xml:3:5-58:857: AAPT: error: resource android:attr/fontVariationSettings not found.
  /Users/<USER_NAME>/.gradle/caches/transforms-1/files-1.1/drawee-1.10.0.aar/603dac14beab9d235ac515ad5b5f1fe7/res/values/values.xml:3:5-58:857: AAPT: error: resource android:attr/ttcIndex not found.

  error: failed linking references. 

ググってみるとcompileSdkVersionを上げると解決するらしい。27だったので28にして実行したら解決した。
ちなみにtargetSdkVersionも上げると別のエラーがいろいろと…
いつか上げなきゃいけない日が来ますが今はとりあえず動けばいいのでそのままで。

androidはとりあえず上記のエラーだけで起動しました。動作確認も特に問題なく一安心。

iOS

iOSは色々と厄介で何度もキャッシュ消したりして時間がかかりました。

** BUILD FAILED **

The following commands produced analyzer issues:
        Analyze /Users/<USER_NAME>/<PROJECT_NAME>/node_modules/react-native/ReactCommon/jsi/jsi.cpp normal x86_64
        Analyze /Users/<USER_NAME>/<PROJECT_NAME>/node_modules/react-native/ReactCommon/yoga/yoga/Yoga.cpp normal x86_64
        Analyze Base/RCTModuleMethod.mm normal x86_64
(3 commands with analyzer issues)

The following build commands failed:
        CompileC /Users/<USER_NAME>/<PROJECT_NAME>/ios/build/CRIA/Build/Intermediates.noindex/RNFirebase.build/Debug-iphonesimulator/RNFirebase.build/Objects-normal/x86_64/RNFirebaseMessaging.o RNFirebase/messaging/RNFirebaseMessaging.m normal x86_64 objective-c com.apple.compilers.llvm.clang.1_0.compiler
(1 failure)

iOSだとエラーで止まると最後にこのようなログが出てきますが、ここには具体的なエラーの内容が書かれてないのでそのままググってもあまり意味ないです。
大量のログを少しずつ遡ってerror catchあるいは1 error generated.と出力されている直前の行にエラーの中身が出力されています。

pch was compiled with module cache path ~ , but the path is currently ~

ios/Build/PROJECT_NAME/ModuleCache.noindexフォルダを削除すると解決。

'folly/Portability.h' file not found

https://github.com/facebook/react-native/issues/24192#issuecomment-479497777
ここのpod 'Folly'以下をPodFileに追加してpod installする。
それでもエラーが出る場合はios/PodsフォルダとPodfile.lockを削除して再度pod installする。

info ** BUILD SUCCEEDED **

info Installing build/CRIA/Build/Products/Debug-iphonesimulator/<APP_NAME>.app
An error was encountered processing the command (domain=NSPOSIXErrorDomain, code=2):
Failed to install the requested application
An application bundle was not found at the provided path.
Provide a valid path to the desired application bundle.
Print: Entry, ":CFBundleIdentifier", Does Not Exist
error Command failed: /usr/libexec/PlistBuddy -c Print:CFBundleIdentifier build/CRIA/Build/Products/Debug-iphonesimulator/<APP_NAME>.app/Info.plist
Print: Entry, ":CFBundleIdentifier", Does Not Exist
. Run CLI with --verbose flag for more details.
error Command failed with exit code 1.

あー…ここでこのエラーですか…
ネットにある色んな解決方法を大方試してみるも解決せず…

埒が明かないのでXcodeからアプリを実行してみる。(実行するときはxcworkspaceから実行しないとエラーになるので注意)

……何事もなくアプリが起動:joy:
なんでよ…と思いつつ再度コマンドで実行してみるもやっぱりダメ。

この後更に色々試しましたが、結局解決できなかったのでめんどくさいけどXcodeから実行することにしましたorz

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

1年に1回くらいAndroidでNFC(FeliCa)をいじる人間のメモ(2019年初夏)

FeliCaの仕事はずいぶんやってないので備忘メモ。
本当はKotlinで書きたいけど、過去のソースとか参考にしたいので一旦Javaで書きます。

やりたいこと

  • IDmを取得したい
  • とりあえずJavaを利用(後でKotlinで書き直す)
  • 開発環境はAndroid Studio(3.4をMacで利用)

仕様

ネットに多存在するサンプルの多くはNfcAdapter.enableForegroundDispatch()を利用してアプリがフォアグラウンドにある間ずっと読みっぱなしで、認識したらIntentde処理するものが多いようですが、この仕様だと読むタイミングや機能のOn/OffのコントロールしにくいのでNfcAdapter.enableReaderMode()を利用してみます。

完全にイベントドリブンで読取りってできないのかな?要はonClickで読取りみたいなことしたいのですが、スマートなやり方がわかりません。誰か教えて。

仕様の概要

アプリの動きは下記のような感じ

  • READER MODE ONボタン(btn01)で読取りスタート
  • 読み取ったらTextView(txt01)に表示
  • READER MODE OFFボタン(btn02)で読取り中止

下記のような動き

スクリーンショット 2019-05-29 15.16.26.png

注意事項

Reader/Write機能をOnに

Reader/Writer機能を利用するアプリを開発する場合は、Android(9.0の場合)の設定で[設定]->[接続機器]->[接続の設定]->NFC[NFC/おサイフケータイ設定]->[Reader/Write,P2P]機能をOnにしておく必要があります。

既存アプリをアンインストール(可能なら)

他のNFC機能を利用するアプリ、特にバックグラウンドで待ってIntentを発生させるようなアプリ(例えば、おサイフケータイアプリ)は開発に影響があるので不要なら削除しておいたほうがいいでしょう。

一方、一般向けアプリの場合は利用者が他のNFCアプリをインストールしていることを前提に仕様を考えておく必要があるでしょう。

実装

無駄が多いですが、各種主要コード全体を貼り付けておきます。

AndroidManifest.xml

NFC利用のパーミッションを追加。

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jp.bluecode.buttontest">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

+   <uses-permission android:name="android.permission.NFC" />

</manifest>

activity_main.xml

あまり参考になりませんが、とりあえず。
画面は自分で適当にレイアウトした方が早いかも。

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/txt01"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="Read ID ..."
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.308" />

    <Button
        android:id="@+id/btn01"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text=" Reader Mode On"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/txt01"
        app:layout_constraintVertical_bias="0.107" />

    <Button
        android:id="@+id/btn02"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Reader Mode Off"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btn01"
        app:layout_constraintVertical_bias="0.116" />

</android.support.constraint.ConstraintLayout>

MainActivity

できるだけ要点だけ短く書くために端折ってます。
IDm取得するところまでなら、NfcAdapter作って、Tagを取得すれば、Tag.getId()という感じでIDmを取得できる。
Javaには標準でbyte列をStringにする関数が無いのでカスタム関数で用意してますが、そっちのほうが長いくらい。

MainActivity.java
package jp.bluecode.buttontest;

import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.os.Handler;
import android.os.Looper;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import java.util.Formatter;
import java.util.Locale;

public class MainActivity extends AppCompatActivity {

    //Viewで使う変数を初期化(別にここじゃなくてもいいけど)
    TextView txt01;
    Button btn01;
    Button btn02;

    //NfcAdapterを初期化
    NfcAdapter nfcAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //UIのマーツをマッピング
        txt01 = findViewById(R.id.txt01);
        btn01 = findViewById(R.id.btn01);
        btn02 = findViewById(R.id.btn02);

        //nfcAdapter初期化
        nfcAdapter = NfcAdapter.getDefaultAdapter(this);

        //Reader Mode Offボタンのenabledをfalseに(トグルにするため)
        btn02.setEnabled(false);

        btn01.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //トグル機能
                btn01.setEnabled(false);
                btn02.setEnabled(true);

                //Redermode On
                nfcAdapter.enableReaderMode(MainActivity.this,new MyReaderCallback(),NfcAdapter.FLAG_READER_NFC_F,null);
            }
        });

        btn02.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                //トグル機能
                btn01.setEnabled(true);
                btn02.setEnabled(false);

                //Readermode Off
                nfcAdapter.disableReaderMode(MainActivity.this);

                //表示初期化
                txt01.setText("Read ID ...");
            }
        });
    }

    //Callback Class
    private class MyReaderCallback implements NfcAdapter.ReaderCallback{
        @Override
        public void onTagDiscovered(Tag tag){

            Log.d("Hoge","Tag discoverd.");

            //get idm
            byte[] idm = tag.getId();
            final String idmString = bytesToHexString(idm);

            //idm取るだけじゃなくてread,writeしたい場合はtag利用してごにょごにょする

            //親スレッドのUIを更新するためごにょごにょ
            final Handler mainHandler = new Handler(Looper.getMainLooper());
            mainHandler.post(new Runnable() {
                @Override
                public void run() {
                    txt01.setText(idmString);
                }
            });

        }
    }

    //bytes列を16進数文字列に変換(めんどい)
    public static String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();

        Formatter formatter = new Formatter(sb);
        for (byte b : bytes) {
            formatter.format("%02x", b);
        }

        return sb.toString().toUpperCase(Locale.getDefault());
    }
}

簡単ですが以上です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DialogFragmentでProgress Dialogを作った

デモ

progress.gif

ソースコード

ライブラリ化はしていないので、使う場合はコピペしてください。

使い方

MainActivity.kt
class MainActivity : AppCompatActivity() {

    companion object {
        const val TAG_PROGRESS_DIALOG = "progress"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        button_show_dialog.setOnClickListener {
            showProgressDialog()
        }
    }

    private fun showProgressDialog() {
        val dialog = ProgressDialogFragment.newInstance()
        dialog.show(supportFragmentManager, TAG_PROGRESS_DIALOG)
        GlobalScope.launch(Dispatchers.Main) {
            dialog.setMessage("Running (10%)")
            dialog.setProgress(10)
            delay(1000)
            dialog.setMessage("Running (50%)")
            dialog.setProgress(50)
            delay(1000)
            dialog.setMessage("Running (100%)")
            dialog.setProgress(100)
            delay(1000)
            dialog.dismiss()
        }
    }
}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

右詰めで画像(view)を置く方法(android)

左側にテキスト、画像を置こうとしたのですが、ふと「そんなコマンドあったっけな」と疑問に思いました。

きっとテキストみたいに右揃え的なのがあるんだろうと思ったのですがそういったものはヒットせず。。。

調べてみるとlayout_weightを設定するのが一般的なようでした。
これを使う時って普通はwidthを0にして比を調整すると思いますが、今回はちょっと特殊な使い方をします。

ダミーの空白のviewを置いてそいつに隙間を埋めてもらって右詰めにします。

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="30dp"
        android:orientation="horizontal">
        <TextView
            android:layout_width="30dp"
            android:layout_height="match_parent"
            android:text="これはテキストです"/>
        <!--ダミーの空白-->
        <View
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"/>
        <ImageView
            android:layout_width="20dp"
            android:layout_height="match_parent"
            android:background="@drawable/image" />
    </LinearLayout>

なぜこれで右詰めにできるのか、うまく説明できない、というかあまりよく理解していませんが、layout_weightのデフォルト値は0なので
テキスト: ダミーの空白: 画像 = 0: 1: 0
このような比になっています。
ならテキストと画像は大きさゼロになるのでは?と思いました。
が、多分layout_weightを用いた場合は、固定の大きさは破壊しないのではないかと思います(今回のケースでは、textViewはwidth="30dp"でimageViewはwidth="20dp")
layout_weightを用いるときにwidthやheightを0に設定するのは、この固定値がviewの最小値として機能して、うまく比が調整されないことがあるからではないかと思います。
つまり、layout_weightを設定すると固定の大きさは保持されるのではないかと思います。
↑憶測です。

viewの右寄せくらいの機能、つけて欲しいですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む