- 投稿日:2019-12-15T23:22:42+09:00
GDG DevFest Tokyo 2019 観戦記
はじめに
12/14(土)開催の
GDG DevFest Tokyo 2019
行ってきました。
公式サイトこの記事は個人の備忘録になります。
参加したセッション
ちょっと大きくて見にくいですが、全体のセッションはこんな感じでした。
今回は、普段業務で使うものはもちろん、
普段触れていなくても来年使いそうな技術のキャッチアップ目的で参加してきました。11:30~ 遅刻+企業ブース周り
前日の忘年会で夜遅くまで飲んでいて、当日朝も油断して遅刻して参加しました。
開会式は参加したかったんですが、のんびりする時間が出来たのでのんびり企業ブースを周り、
ノベルティグッズを収集しました。
(CircleCI Tシャツ いただきました)LINEの企業ブースでも結構ゆっくり説明を聞くことができて、いくつか自社公開のOSSを
紹介してもらいました。
https://github.com/line/feature-flag-android
https://github.com/line/lichfeature-flagはこの後のセッションでも語られていた & DroidKaigi2019でも
セッションテーマになっていて、非常に興味を持ったテーマだったので、
実務でも使えるか試してみたいです。
lichはまだあまり実務に投入するイメージが沸かない(そのプロジェクト特有になりそうで)ですが、
結局車輪の再発明になるから後々これにたどり着くことになるのかな...
(間違ってる可能性大ですが、聞いたイメージはAndroid Jetpackのutility)スピーチに多くの方が参加していたので、お昼ご飯も並ばずに食べれたのは良かったです。
(セッションが終わった時間くらいから行列出来てたので...)12:20~ パネルディスカッション
えーじさん、及川さん、田中さんの3名によるキャリアについてのディスカッションでした。
3人ともなかなかインパクトのある経歴でした(そのまま、参考にはならないよね..)印象に残ったのは、
ソフトウェアファーストっていう本に書いてあるので~(及川さん)
実際に、ソフトウェアファースト片手にトークしてたので。
「宣伝すごいな〜笑」と思いましたが、本の中身の一部を使いながら説明をしてもらって、
実際中身はすごくいいんじゃないかと思いました!
(今、カイゼンジャーニー読んでるから、次で..)14:10~ Goの10年の道のりとその変遷
資料は見つからず...印象に残ったのは...
- 言語の祖先にALGOLというのがある
- ALGOLからアメリカ(C)かヨーロッパ(PASCAL)かでいくつかの言語の分派が生まれた
- それぞれの分派のエキスパートが作ったのがGO
- 元はC++のビルドが遅く、言語が大きくなりすぎたため
- GOはプログラミングするための重要な要素に絞って成長した話についていけない場面もいくらかありましたが、最後の部分が今GOが流行に乗ってる理由なのかなぁって思いました..
学習環境( https://go.dev/ ) もあるので、来年は手を付けてみたいです。15:10~ 来年に備えるために Android の知識を網羅する
資料
(qiitaでspeakerdeckって埋め込みできないんですかね...)Android Jetpack から Kotlin まで紹介が広く、個人的には一番刺激を頂いたセッションでした。
薄々感じてましたが、MVVM + Coroutines は今後圧倒的に主流になりそうですね..
MVVM と 何もなし(良くはないけど、時間がないとかで導入出来ない場合) のどちらかが増える予感...以下、メモ。
Android10 highlights
DarkTheme / GestureNavigation / LocationControls
- LocationControls -> BackGroundのLocationはAndroid10で必要になる
- GestureNavigation -> Android10 NavigationBarの透過
- マルチウィンドウ -> 割と以前からある機能、両方は動画流さないとかの制御が必要。
AndroidStudio4.0 highlights
- Jetpack Compose -> 後述
- Motion Editor -> AnimationのGUI実装が可能になる(Stableじゃない)
- MultiPreview -> 開発者に取っては嬉しい機能
Kotlin
- 2019 Kotlin First
- Coroutines RxJava -> Coroutines
- 2020年は Kotlin Flow で Rxの置き換えが主流になるかも
Coroutines メモ
- 軽量スレッド
- cancelが簡単
- 失敗時のハンドリギングが簡単
- launch = Coroutines Builder
- launch の戻り値は Job
- Job同士は親子関係を作ることができる
- 親のキャンセルが子供にも伝播される
- launch は Scope の中で呼び出さないといけない
- Dispatchers.Defaultがいい感じにしてくれる
- UIはMain
- DB、ネットワーキングはIO
- scopeにスレッドも与えることができる
(個人的に Coroutines は今年実務で使い切れなかったので、来年は使い倒したい)
Architecture(MVVM)
- Googleの推奨アーキテクチャとしてMVVMが取り上げられた
- MVVMを設計しやすくするためにjetpack
- Model = ViewModel with LiveData
- Repository = LocalSource / RemoteDataSource
Jetpack
- AppCompat = 古いOSを吸収してくれる
- Android KTX = Kotlinの拡張機能Set
- AAC(AndroidArchitectureComponents) = 堅牢でテストと麺店アンスが簡単なアプリの設計を支援するライブラリ
Navigation
- 画面遷移を先鋭を視覚的に実装できる(Story Board)
- XMLだから自分で定義、修正することもできる
- 画面間のデータを型安全に渡すことができる
ViewModel+LiveData
- ライフサイクルを意識した方法でUI関連のデータ田を保存及び管理できる
- ViewModel - Fragment間のデータ共有、画面回転で破棄されない
- LiveData - Fragment~(メモ取れず...)
- ViewModelのデータをObserveすることが一般的。ViewModelでCoroutinesやFlowが登場する
Retrofit+OKHttp
- OS5.0以上じゃないと動かないとようになっている
- Retrofitは GraphQLには対応していない
DI
- Dagger / Koin / Kodein --- Daggerは難しい
(GDEの方が難しいと言われると、深みが強い...速いは魅力的だけど..)
Test Lab
- クラウド上の端末でテストできるサービス
- instrumentの実行も出来る
- Appiumとかには対応していないし、するつもりもないらしい
16:05~ 休憩
3時間くらい講演を聴き続けたので、休憩&PCの充電...
(充電し忘れて参加したので)後から、これ -> Yearly Web 2019 に参加すれば良かったと気付きましたが..
ハンズラボも周ってみましたが、途中参加むりっぽかったので、すごすごとその場を後に..17:00~ Flutter Overview
資料を熱望します...
以下、資料の写です。自分に取って、重要な一文は、
「FlutterはUIフレームワークである。UIをたくさん、早く作りたい時に有利」です。
UIをFlutterに任せるので、既存のiOSデザインに寄せたい時は選択肢に入れない方がいいと思いました。
新規開発でマテリアルデザインを進めれるなら、有力だと思いました。クロスプラットフォーム、アプリを作るためのUIフレームワーク Flutter Interactにてアナウンス v1.12 Stable Flutter 2年半(2017/5 αリリース) <Flutterの特徴> Dart言語 クロスプラットフォーム 宣言的UI 高い開発効率(ホットリロード) 高い実行パフォーマンス IDE is VSCode 高い実行パフォーマンス -> ネイティブ・コンパイル => ARM(スマホは全部これ) x86 ★★★Flutterの大きな特徴★★★ GPU活用 Skia(2D Graphicsのライブラリ) Dart version 2.7(最新は) Static typed(コンパイル型言語に近い特徴、文法もJavaに似ている) JIT + VM AOT Javascriptの置き換えを目指した Flutter向きなアプリとは? UI(部品や画面)の多いもの FlutterのUIの作りやすさの恩恵が大きく受けられる ex. MediaNewsのアプリ、SNS etc... 向かないもの 少数画面、機能特化のもの カメラ、動画、地図...など? ゲーム 作れはするが、ゲーム特化のフレームワークではない Flutterの検討シーン 新規アプリの開発(既存のコード資産がない場合) プロトタイプ 既存アプリへの追加 Add-to-App Flutter部分とネイティブ部分は別れてしまう、ネイティブで実装したコードの利用/流用がやりにくい Flutterむきなアプリとは? FlutterはUIフレームワークである。UIをたくさん、早く作りたい時に有利。 Flutterのアーキテクチャ フルスタック レイアウト計算から画面描画までFlutterがやる ネイティブのUIフレームワークを利用しない Flutterのアーキテクチャ Pros: プラットフォーム間の違いが小さい Cons: ネイティブとの混在が難しい ネイティブ連携 プライグイン機構 DartからAndroid/iOSを呼び出せる。 デバイス依存のもの(カメラ、GPSなど)を使うことができる flutter.dev/showcase in Japan -> CARTUNE Web アプリをそのままWebに持っていく時に使う インタラクティブ性が高く、グラフィックを多用したもの HTMLのドキュメントの構造化の置き換えではない(SEOには対応していない、難しそう)まとめ
こういう記事書くなら(当初そんなに書こうと思ってなくて..)予め写真もっと撮影しておけば良かったです。
とはいえ、初めて参加したイベントでしたが、自分の業務や興味とも関連していて満足度高かったです!
場所が大学だったので、懐かしい気持ちになれました!今年のまとめを聞いてましたが、もう追い切れないくらい知りたいこと、興味あることが多いですね。
イベントでその道のトップクラスの人から知見や情報を一気に得ることが出来たのは、
このイベントに参加して良かったと思いました。
- 投稿日:2019-12-15T22:51:47+09:00
[Android] Kotlinのラムダ式でonClickListenerとonLongClickListenerを両立
Androidのアプリにてボタンなどを長押しする時の処理とタップしたときの処理を変えたい場合があります。
だいぶKotlinにも慣れてきて、ボタンなどのonClickListenerもラムダ式で書くようにしていますが、表題の通り、Kotlinのラムダ式を使って
onClickListener
とonLongClickListener
を両立するのに躓いたのでメモしておきます。
onClick
イベントは通常returnを持ちませんが、onLongClick
イベントはBoolean
型の戻り値を持ちます。この戻り値をtrue
とするとonClick
とonLongClick
が干渉しなくなります(公式Reference)。ですが、あれ?ラムダ式の戻り値ってどう書くんだ?となって調べてこちらのサイトのラムダ式の項にて
return 文は使用せず、ラムダ式の末尾に記述した値が評価され、戻り値となる。
とのことだったので、このように実装しました(ボタンのインスタンスは
button
)。button.setOnClickListener { Toast.makeText(this@MainActivity, "OnClick", "Toast.LENGTH_SHORT).show() } button.setOnLongClickListener { Toast.makeText(this@MainActivity, "OnLongClick", "Toast.LENGTH_SHORT).show() true // trueを返す }こちら、最後の
true
のreturnはreturn@setOnLongClickListener trueの省略形のようですね。
- 投稿日:2019-12-15T21:25:44+09:00
【Androidアプリ入門】郵便番号から住所取得アプリを作ってみた♪
今回はAndroidアプリにPython連携するには、Webapi+kotlin使った方が簡単ですよという助言を受けて、Webapiっぽいものを理解するために郵便番号から住所取得するアプリを作ってみました。
このアプリは以下の参考の第11章非同期処理とWeb API連携のコードを参考にしています。
【参考】
Androidアプリ開発の教科書 Kotlin対応
また、郵便番号検索API@zipcloudを利用しています。
ちなみに、このサービスを利用すると、ブラウザ立ち上げて以下を入力すれば、その下のような情報得られます。http://zipcloud.ibsnet.co.jp/api/search?zipcode=7830060 { "message": null, "results": [ { "address1": "高知県", "address2": "南国市", "address3": "蛍が丘", "kana1": "コウチケン", "kana2": "ナンコクシ", "kana3": "ホタルガオカ", "prefcode": "39", "zipcode": "7830060" } ], "status": 200 }ということで、今回作るアプリはそれ自身の価値はあまりないといえますが、あくまで動きを理解するために作ります。
やったこと
・登場する変数定義
・表示/配置を決める
・非同期処理と郵便番号を渡す・登場する変数定義
使い方は、
①郵便番号を入力する
②保存ボタンを押す(Listに郵便番号を渡し表示)
③List表示された郵便番号をクリックする
これで、郵便番号に対応した住所が表示されます。strings.xml<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">住所取得</string> <string name="bt_click">保存</string> <string name="post_name">郵便番号を入力してください</string> <string name="tv_winfo_title">郵便番号の住所詳細</string> </resources>・表示/配置を決める
工夫した点
①表示サイズを適当に決めました
②グループ分けしてまとめたり横に並べたりして最終的に以下の配置にしました
③Listやボタンなどが入力ボードが現れると消えてしまったり、検索結果が出ると消えてしまうのでそれが消えないように配置しました
activity_main.xml<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/post_name"/> <EditText android:id="@+id/etName" android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="text" /> <Button android:id="@+id/btClick" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/bt_click" /> <TextView android:id="@+id/tvOutput" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="" android:textSize="15sp" /> <ListView android:id="@+id/lvCityList" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="25dp"/> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="0dp" android:layout_weight="0.5" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="0dp" android:gravity="center" android:text="@string/tv_winfo_title" android:textSize="15sp"/> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="0dp" android:orientation="horizontal"> <TextView android:id="@+id/tvCityName" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="0dp" android:textSize="15sp"/> <TextView android:id="@+id/tvWeatherTelop" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="0.5" android:textSize="15sp"/> </LinearLayout> <ScrollView android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/tvWeatherDesc" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="20sp"/> </ScrollView> </LinearLayout> </LinearLayout>・非同期処理と郵便番号を渡す
改造は以下のとおり
もとはList表示させて項目(地域)をクリックするとそこのお天気情報を表示していたが、クリックすると郵便番号から住所を取得する。
またもとは事前にListに入れた地方のみしか表示できなかったが、入力してListに渡すようにした。
以上を実施するために
①郵便番号を入力し保存ボタンを押すと入力した郵便番号をListに渡す
②Listの項目を押した後の動きは以前のままの動作である
③表示は取得したデータをそのまま表示することとしたMainActivity.ktpackage com.websarva.wings.android.asyncsample import ... class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) //表示ボタンであるButtonオブジェクトを取得。 val btClick = findViewById<Button>(R.id.btClick) //リスナクラスのインスタンスを生成。 val listener = HelloListener() //表示ボタンにリスナを設定。 btClick.setOnClickListener(listener) } /** * ボタンをクリックしたときのリスナクラス。 */ private inner class HelloListener : View.OnClickListener { override fun onClick(view: View) { //名前入力欄であるEditTextオブジェクトを取得。 val input = findViewById<EditText>(R.id.etName) //メッセージを表示するTextViewオブジェクトを取得。 val output = findViewById<TextView>(R.id.tvOutput) //入力された名前文字列を取得。 val inputStr = input.text.toString() //idのR値に応じて処理を分岐。 when(view.id) { //表示ボタンの場合… R.id.btClick -> { //入力された名前文字列を取得。 val inputStr = input.text.toString() output.text = inputStr val st = inputStr //"1240004" //画面部品ListViewを取得 val lvCityList = findViewById<ListView>(R.id.lvCityList) //SimpleAdapterで使用するMutableListオブジェクトを用意。 val cityList: MutableList<MutableMap<String, String>> = mutableListOf() //都市データを格納するMutableMapオブジェクトの用意とcityListへのデータ登録。 //val st = "1240004" var city = mutableMapOf("name" to "郵便番号; " + st, "id" to st) cityList.add(city) //SimpleAdapterで使用するfrom-to用変数の用意。 val from = arrayOf("name") val to = intArrayOf(android.R.id.text1) //SimpleAdapterを生成。 val adapter = SimpleAdapter( applicationContext, cityList, android.R.layout.simple_expandable_list_item_1, from, to ) //ListViewにSimpleAdapterを設定。 lvCityList.adapter = adapter //リストタップのリスナクラス登録。 lvCityList.onItemClickListener = ListItemClickListener() } } } } /** * リストがタップされたときの処理が記述されたメンバクラス。 */ private inner class ListItemClickListener : AdapterView.OnItemClickListener { override fun onItemClick(parent: AdapterView<*>, view: View, position: Int, id: Long) { //ListViewでタップされた行の都市名と都市IDを取得。 val item = parent.getItemAtPosition(position) as Map<String, String> val cityName = item["name"] val cityId = item["id"] //取得した都市名をtvCityNameに設定。 val tvCityName = findViewById<TextView>(R.id.tvCityName) tvCityName.setText(cityId + "の住所: ") //WeatherInfoReceiverインスタンスを生成。 val receiver = WeatherInfoReceiver() //WeatherInfoReceiverを実行。 receiver.execute(cityId) } } /** * 非同期でお天気データを取得するクラス。 */ private inner class WeatherInfoReceiver() : AsyncTask<String, String, String>() { override fun doInBackground(vararg params: String): String { //可変長引数の1個目(インデックス0)を取得。これが都市ID val id = params[0] //都市IDを使って接続URL文字列を作成。 //val urlStr = "http://weather.livedoor.com/forecast/webservice/json/v1?city=${id}" val urlStr = "http://zipcloud.ibsnet.co.jp/api/search?zipcode=" + id //URLオブジェクトを生成。 val url = URL(urlStr) //URLオブジェクトからHttpURLConnectionオブジェクトを取得。 val con = url.openConnection() as HttpURLConnection //http接続メソッドを設定。 con.requestMethod = "GET" //接続。 con.connect() //HttpURLConnectionオブジェクトからレスポンスデータを取得。天気情報が格納されている。 val stream = con.inputStream //レスポンスデータであるInputStreamオブジェクトを文字列(JSON文字列)に変換。 val result = is2String(stream) //HttpURLConnectionオブジェクトを解放。 con.disconnect() //InputStreamオブジェクトを解放。 stream.close() //JSON文字列を返す。 return result } override fun onPostExecute(result: String) { val tvWeatherDesc = findViewById<TextView>(R.id.tvWeatherDesc) //tvWeatherTelop.text = telop tvWeatherDesc.text = result //desc } /** * InputStreamオブジェクトを文字列に変換するメソッド。変換文字コードはUTF-8。 * @param stream 変換対象のInputStreamオブジェクト。 * @return 変換された文字列。 */ private fun is2String(stream: InputStream): String { val sb = StringBuilder() val reader = BufferedReader(InputStreamReader(stream, "UTF-8")) var line = reader.readLine() while(line != null) { sb.append(line) line = reader.readLine() } reader.close() return sb.toString() } } }まとめ
・非同期処理を利用して郵便番号から住所を取得するアプリを作ってみた
・入力とボタンを配置して入力データにより、Listデータを変更出来るようにした・これでお天気も地域を入力すれば検索できるのでやってみる
・音声入力もできるが郵便番号はそのまましばらくすると783‐0060と―が入ってしまう
- 投稿日:2019-12-15T18:27:47+09:00
Android端末内マイクロサービスという謎アーキテクチャの提案
Android + Microservices
Androidアプリのバックエンドをマイクロサービスにする話ではありません。
Android端末内で複数アプリを用いたマイクロサービスアプリ開発です!!
つまり、変態近未来アーキテクチャの紹介です。(普通のアプリを書いている人には恐らく無縁の技術です。)
でも、一部有用な内容もあるので、とりあえず読んでいってください。導入
Androidは、ほかのアプリのActivityやServiceにIntentを飛ばせます。
暗黙的IntentとActionを使う方法(特定の機能を持つアプリに遷移する)は有名だと思います。しかし、この暗黙的Intentを使う方法には一つ大きな欠点があります。
Serviceを叩けないという最大の問題です。
起動しているアプリの裏で複数のアプリが連携するためにはActivityではなく、Serviceを動かす必要があります。どうしても複数のServiceが連携し、Microservice Android Applicationを実現したい・・・!!
この野望に答える手法がありました。
実は明示的Intent(特定のクラスに遷移する)で、他のアプリのActivityやServiceを直接呼ぶことができます。実際のところ、他のアプリのActivityやServiceを明示的Intentで呼び出すことなど、普通はありえません。
何故ならば、ユーザが呼び出し先のアプリをインストールしてくれている保証が無いからです。
しかし、この手法を使えば複数のアプリがデータをやり取りして一つの機能を実現するマイクロサービスAndroidアプリケーションが作れます!
有用なユースケースは思いつきませんが、面白いと思いませんか?あと、Microservice Android Applicationがかっこいい・・・
(Dynamic Feature Moduleに似ていますが、よりダイナミックになっています)
目次
- Android内マイクロサービスアーキテクチャの説明
- 基本編: Serviceとデータやり取りする方法
- 変わった人向け: 他のアプリのServiceやActivityを呼び出す方法
- 変態向け: 端末内にインストールされているアプリのスキャン方法
- で結局何ができるの?
Android内マイクロサービスアーキテクチャの説明
例えばこんな構成が作れます
Host AppからPlugin Appに対してサーバへの通信を依頼し、最終的にその結果を受け取るアプリが作れます!
(別のアプリに分ける必要がある?というツッコミは野暮ってもんですよ。)基本編: Serviceとデータをやり取りする方法(アプリ内、アプリ間共通)
AndroidのServiceは主に4種類あります。サービスについて
- background Service
- Intent Service
- foreground Service
- bind Service(実はIntentServiceはbackgroundServiceの仲間だったりするのですが、分かりやすく4種類としています)
このうち、呼び出し元に値を返しやすいのは
- Intent Service
- bind Service
です。
IntentServiceはresultReceiver
というクラスを使って呼び出し元に値を返すことができます。以下、resultReceiverのサンプルコードです。
//ResultReceiverの引数は実行スレッドを指定するもの。nullなら任意のスレッド this.startIntentService(Hoge::class.java, object: ResultReceiver(null) { override fun onReceiveResult(resultCode: Int, resultData: Bundle?) { when (resultCode) { // 予めIntent先と決めておいた定数 RESULT_CODE -> { resultData?.getSerializable(Fuga) // 以下略 } else -> {} } } })BindServiceは3種類の値の返し方がありますが、よく使われるのは
Messenger
クラスを使った方法です。
Messenger
についてはこの後詳しく説明します。
3種類の返し方については公式に詳しく書いてあります。ここまでは一般的なServiceの話です。ここからアプリ間通信の話に踏み込みます。
変わった人向け: 他のアプリのServiceやActivityを呼び出す方法
bindService
は他のアプリから呼び出せますが、IntentService
は呼び出せません。
なので、ここから先はbindService
を使います。bindServiceについてはこちら基本的にはアプリ内のbindServiceを呼び出す方法と変わりません。
しかし、他アプリゆえに気をつけるポイントが3点あります。serviceの可視性を
exported=true
にするAndroidManifest.xmlのService項目に
exported=true
を設定しましょう。
この設定が無いとアプリ外からIntentで呼べません。AndroidManifest.xml
<service android:name=".HogeService" android:enabled="true" android:exported="true"> </service>
Messenger
クラスでデータのやり取りをするHost AppもPlugin Appも互いのクラスを知りえません(別アプリなので当然)。
なので、IBinder
を拡張する方法は使用できません。
Messenger
にBundle
を詰めてやり取りします。
HTTP通信で例えるならば、json
に値を詰めてやり取りするイメージです。class SampleBindService : Service() { private val messageHandler = Handler { msg -> when (msg.what) { // msg.whatに0が来たらログに吐く 0 -> { Log.d("SampleBindService", "message: ${msg.obj}") } else -> {} } val bundle = Bundle().also { it.putString("sample", "This is test.") } // what=0にbundleデータを詰めてreplyする msg.replyTo?.send(Message.obtain(null, 0, bundle)) // replyは何度でも送ることができる msg.replyTo?.send(Message.obtain(null, 0, bundle)) true } private val messenger: Messenger = Messenger(this.messageHandler) override fun onBind(intent: Intent): IBinder { return this.messenger.binder } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { return super.onStartCommand(intent, flags, startId) } }Host AppはPlugin Appの
applicationId
とサービスのclassName
(FQDN)を知っていることIntentで呼び出すときに、呼び出し先の
applicationId
とclassName
が必要になります。
つまり、なんらかの方法でHost AppはPlugin Appの上記2つの情報を知る必要があります。
そして、その方法は変態向け: 端末内にインストールされているアプリのスキャン方法で解説します。val intent = Intent().also { it.setClassName(applicationId, className) }変態向け: 端末内にインストールされているアプリのスキャン方法
ここから先はほとんど使う人が居なさそうな機能の紹介です。
先ほど、Host AppはPlugin Appの
applicationId
とclassName
を知っていなければならない。という話をしました。
1人でHost AppとPlugin Appの両方を作る場合は、これらの情報を知っているはずなので問題ありません。
一方で、Microserviceのように連携する場合やPlugin AppがHost Appの機能拡張アプリであった場合、Host AppはPlugin Appの存在を知りえません。(プラグインは本体を意識するが、本体がプラグインを意識したら変ですよね?)では、いかにしてHost AppはPlugin Appの存在を知るのか。
その答えは・・・Host Appが端末内にインストールされているアプリをスキャンすれば良いのです。
(実はAndroidのアプリは端末内のアプリをスキャンすることができます。)詳しくはこの記事をお読み下さい。
ただ、1点上記の記事と異なる点があります。上記の記事ではActivityを呼び出していましたが、今回はServiceを呼び出します。
LAUNCHERとして登録されているActivityはアプリスキャンで検索できるのですが、それ以外のActivityとServiceは検索できません。(少なくとも私は知らないので、知っていたら教えてください。)
ですので、ServiceのclassName
はスキャンしても知ることはできません。
なので、苦肉の策として、Plugin Appが提供するServiceは
applicationId
と同じパッケージの直下にPluginService
という名前で置く
などの約束をして、Host AppがIntentを飛ばせるようにする必要があります。スキャンをした後にIntentを飛ばすサンプルコードが以下です。
val pm = packageManager val packageInfoList = pm.getInstalledPackages( PackageManager.GET_ACTIVITIES or PackageManager.GET_SERVICES ) val targetPackageName = packageInfoList // 特定のパッケージで始まるアプリを検索対象とする .filter { it.packageName.startWith("jp.co.hoge.fuga") } .map { it.packageName } }.first() // 今回は見つかる前提。見つからないとここで落ちる val intent = Intent().also { // 上記で得られたapplicationIdとclassNameを詰める(classNameはパッケージ直下にPluginServiceで固定) it.setClassName(targetPackageName, targetPackageName + ".PluginService") } val connection = object: ServiceConnection { override fun onServiceConnected(name: ComponentName, service: IBinder) { val messenger = Messenger(service) messenger.send(Message.obtain(null, 0, null).also { it.replyTo = Messenger(ReplyHandler()) }) } override fun onServiceDisconnected(name: ComponentName) { } } bindService(intent, connection) }で、結局何ができるの?
例えば、私が思いつくのは以下のような物です。
しかし、複数アプリを連携させてまでやるものかと言われると。うーん案1: ニュースまとめアプリ
各種ニュースサイトをまとめて見れるアプリ。
各Plugin Appはそれぞれのニュースサイトからの情報を取ってくる昨日を有し、
Host AppはPlugin Appから得たデータを元に、画面を作る。ユーザはPlugin Appを入れれば入れるほど、様々なニュースサイトから情報を集められるようになる。
(でも、それってDynamic Feature Moduleでよくね?そもそもプラグイン構成取る必要ある?最初から様々なサイトから集めておけよ。)
案2: みんなで情報提供Serviceを公開し合う文化を作り、Microserviceを実現
この記事を読んでくれた人がAndroidを書いたときに、何かしらのデータを提供するbindServiceを作り、それを外部に公開したとする。
そして、そのApplicationId
やclassName
を公開したとする。
これらの情報が蓄積されれば、互いに互いのServiceを叩き合うMicroserviceの誕生!!かっこいい!!(セキュリティ的に大丈夫なんかとか、依存したいアプリが入っているかわからない問題が課題。)
何か使える案下さい
面白そうな機能を見つけたので、変な構想を練って見ましたが、何に使えるのかイメージが湧きません・・・
一番のネックは、ユーザがアプリを入れてくれるとは限らない点ですね。
そこさえ克服できれば、アプリ間をService同士が連携し合う、Android内Microserviceアーキテクチャが陽の目を見る日も近い!
- 投稿日:2019-12-15T17:21:46+09:00
UnityでAndroid実機で実行したときに一部環境で画面が乱れる問題と対処法
発生した問題
UnityでAndroidスマートフォン向けの2Dゲームを開発していてゲーム画面のアスペクト比を統一させようとした。
方法はスクリプトでスマホ画面のアスペクト比が基準となるアスペクト比(今回は16:9)と異なる場合、ゲーム開始時にメインカメラのrectを変更してゲーム画面のアスペクト比を統一して余ったところは黒帯を表示させることで対応した。
エディタ上でゲームを実行したところ下の写真のようにカメラ外の左右の余った部分は黒帯が表示されている。
しかし、ビルドして手元のPixel 3aで実行したところ、下の写真のように左右のカメラの表示外の部分が黒帯にならず表示が乱れる現象が発生した。
他の実機端末で実行するとエディタ上で実行したように、余った部分は黒く表示されて同じ現象は発生しなかった。原因
調べたところ下の記事に書いてあるようにカメラの描写外の部分はメモリ上のゴミが残されているかららしい。
Android の特定機種で画面が乱れたら……対処法
画面全体を映す二つ目のカメラを用意してメインカメラの描写外を表示させることによって一番下の画像のように問題が起こってた端末でもエディタ上と同じく表示されるようになった。全体を映す二つ目のカメラの描写の上にメインカメラを描写してるイメージ。二つ目のカメラはCulling Maskをすべてのレイヤーのオブジェクトを表示しない設定にして黒帯の部分にオブジェクトが映るのを回避している。二つ目のカメラのDepthをメインカメラより小さくしないと二つ目のカメラが上に表示されて全体真っ黒な画面になるので注意。(今回の場合だとBackgroundの色を変えることでほかの色の帯にすることが可能)
ゲームでどのようにカメラを使用しているかによって対処法も変わりそう。
さいごに
もし間違えている箇所などがありましたらコメント等で教えてください
参考
- 投稿日:2019-12-15T16:09:45+09:00
Maps SDK for Androidチュートリアル
Androidで地図を表示する場合、第一の選択肢となる Maps SDK for Android の使い方についてまとめてみました。
Maps SDK for Androidとは
Google製のマップを表示するAndroid用のSDKです。地図の表示だけならモバイルは無制限に利用できます。iOSやブラウザ用もあります。
準備
API Keyを取得する必要があるので、Maps SDK for Android - Get an API key を参照してAPIキーを作成してください。
- GCPコンソールにいく
- gcpプロジェクト作成
- 認証情報に行き、API key生成
- API keyの制限
API keyの制限で、特定のパッケージ名のアプリに限定するためには、証明書に合わせたSHA-1のフィンガープリントを作成する必要があります。
AndroidStudioの新規プロジェクト作成で、「Google Maps Activity」を選択し作成しましょう。選択しなくてできますが、その場合SDKの指定や、Manifestなどの書き換えが必要になります。
google_maps_api.xml
というファイルが作成されているので、APIキーを指定します。google_maps_api.xml<resources> <string name="google_maps_key" templateMergeStrategy="preserve" translatable="false">YOUR_KEY_HERE</string> </resources>アプリをビルドするとマップが表示されます。「Google Maps Activity」の自動生成だと、シドニーが指定されています。
地図を表示する
地図を表示する方法を具体的に見ていきましょう。(「Google Maps Activity」の自動生成だとコードはすでに作成されています。)
Mapを表示する際に、MapFragment
を使う方法と、MapView
を使い方法があります。
ここでは前者の方法を見ます。地図を表示したいactivityのレイアウトに、 以下のFragmentを記載します。
android:name="com.google.android.gms.maps.MapFragment"
を指定するとActivityに自動的にAreaMapFragment
が適用されます。(ActivityのコードからMapFragmentを追加することもできます)activity_main.xml<?xml version="1.0" encoding="utf-8"?> <fragment xmlns:android="http://schemas.android.com/apk/res/android" android:name="com.google.android.gms.maps.MapFragment" android:id="@+id/map" android:layout_width="match_parent" android:layout_height="match_parent"/>次に、表示するActivityで
OnMapReadyCallback
インターフェースを実装します。MainActivity.ktclass MapsActivity : AppCompatActivity(), OnMapReadyCallback { private lateinit var mMap: GoogleMap override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_maps) val mapFragment = supportFragmentManager .findFragmentById(R.id.map) as SupportMapFragment mapFragment.getMapAsync(this) } override fun onMapReady(googleMap: GoogleMap) { mMap = googleMap } }
supportFragmentManager.findFragmentById(R.id.map)
で、xmlに記載したMapFragmentのidを指定します。
getMapAsync()
でコールバックがセットされます。mapの準備ができたら、
onMapReady
が呼ばれます。以降、マーカーを設置したり、マップを動かしたりすることができるようになります。スタイルを変更する
マップの全体のデザインを変更したいときは、簡単に指定することができます。Maps Platform Styling Wizardという、スタイルを作成するツールが用意されているので、容易に自分好みのスタイルを作ることができます。
(しかもiOS、ブラウザでも共通のスタイルを利用することができます)ツールで地図をカスタマイズすると、jsonが生成されるので、リソースに
raw
フォルダを作成し追加します。
onMapReady
でstyleを先ほどのjsonファイルを指定すると、マップに反映されます。MainActivity.ktoverride fun onMapReady(googleMap: GoogleMap) { // style val mapOptions = MapStyleOptions.loadRawResourceStyle(this, R.raw.style_map) googleMap.setMapStyle(mapOptions) }マーカーを設置する
マーカーの設置は、緯度経度やタイトル、タップしたときの情報などを指定してセットすることができます。
MainActivity.ktval skytree = LatLng(35.710063, 139.8107) val markerOption = MarkerOptions() .position(skytree) .anchor(0.5f, 0.5f) // マーカーの位置をずらせます .title("skytree marker") // iconをタップすると表示されます .snippet("The tallest tower in Japan.") // iconをタップすると表示されます .icon(BitmapDescriptorFactory.fromResource(R.drawable.skytree)) mMap.addMarker(markerOption) mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(skytree, 15f))円を表示する
円を描画することもできます。
MainActivity.ktmMap.addCircle( CircleOptions() .center(skytree) // 中心の緯度経度 .radius(500.0) // 円の半径 .strokeColor(0x500000FF) // 境界線の色 .strokeWidth(5f) // 境界線の太さ .fillColor(0x0F0000FF) // 円内部の色 )カメラを動かす
パッと切り替える方法と、アニメーションしながら切り替える方法があります。
MainActivity.ktmove_button.setOnClickListener { mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(tokyoTower, 15f)) }MainActivity.ktanimate_button.setOnClickListener { mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(tokyoTower, 15f)) }マーカーが収まるように表示する
複数のマーカーが地図に収まるように表示したい時があるかと思います。
MainActivity.ktoverride fun onMapReady(googleMap: GoogleMap) { mMap = googleMap mMap.setOnMapLoadedCallback { fitMarkersOnScreen() } } private fun fitMarkersOnScreen() { val skytree = LatLng(35.710063, 139.8107) val kaminariMon = LatLng(35.710233, 139.797648) val asakusaViewHotel = LatLng(35.715151, 139.79829) val bounds = LatLngBounds.Builder().also { builder -> listOf(skytree, kaminariMon, asakusaViewHotel).forEach { mMap.addMarker(MarkerOptions().position(it)) builder.include(it) } }.build() mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, 300)) }
newLatLngBounds
は、地図がレンダリングされたあとで呼び出さないといけません。setOnMapLoadedCallback
で呼び出すようにしましょう。その他
mapFragment.getMapAsync(this)
はonCreate
で呼ぶようにしましう。onResumeなどでよぶと、地図が再生成されてしまうので、表示に時間がかかります。まとめ
Maps SDKを使えば、簡単に地図を表示することがわかったかなとおもいます。良い地図ライフを。
- 投稿日:2019-12-15T14:10:39+09:00
Androidアプリ開発で国民の祝日を表示させるのに試行錯誤。前編
どうも!ヨースケです。日記アプリもようやく1画面が終わったので、約10時間に及ぶ日帰りミニ旅をしていました。一眼レフでたくさん撮るぞーと意気込んでいましたが、ほとんど撮れませんでした...ただ疲れただけで、帰り道に前方後方不注意の車に二度轢かれかけるなどさんざんな一日でした(笑)。
今回は日記アプリに取り入れたその日が国民の祝日ならば表示しタップするとその詳細をダイアログで表示する。というのを前編後編にわたって紹介します。
- 国民の祝日
- 日付が固定されている祝日
- 第〇月曜日の祝日
- 春分と秋分
- 振替休日
国民の祝日
まず、日本にある国民の休日を記してみます。
元日、成人の日、建国記念の日、天皇誕生日、春分の日、昭和の日、憲法記念日、みどりの日、こどもの日、海の日、山の日、敬老の日、秋分の日、スポーツの日、文化の日、勤労感謝の日の16個あります。「体育の日」は2020年に「スポーツの日」と改められました。次はプログラムで重要になる日付の話をします。日付が固定されている祝日
プログラム的には楽な日付が初めから決められている祝日の一覧です。
祝日名 日付 備考 元旦 1月1日 - 建国記念の日 2月11日 - 天皇誕生日 2月23日 - 昭和の日 4月29日 - 憲法記念日 5月3日 - みどりの日 5月4日 - こどもの日 5月5日 - 山の日 8月11日 2020年は8月10日 文化の日 11月3日 - 勤労感謝の日 11月23日 - 第〇月曜日の祝日
祝日名 日付 備考 成人の日 1月の第2月曜日 - 海の日 7月の第3月曜日 2020年は7月23日 敬老の日 9月の第3月曜日 - スポーツの日 10月の第2月曜日 2020年は7月24日 以上の表のようにオリンピック・パラリンピックがある2020年は祝日が動いてしまいますが、月と週と曜日が分かるので何とか判定できそうです。
春分と秋分
この2つの祝日は厄介でした。まず、春分の日は3月の19日~22日うちの1日に、秋分の日は9月の22~24日のうちの1日になります。なぜ年によって日にが異なるのかと言うと地球の公転周期と関係があるようで詳しくはこちらをご覧ください(投げやり)。
春分の日や秋分の日は逐一カレンダーを見ながら入れていかなければならないのか?と最初は肩を落としましたが、Wikipediaによれば西暦年を4で割ったあまりでその年の春分・秋分の日が決められるそうです。
春分の日:https://ja.wikipedia.org/wiki/%E6%98%A5%E5%88%86%E3%81%AE%E6%97%A5
秋分の日:https://ja.wikipedia.org/wiki/%E7%A7%8B%E5%88%86%E3%81%AE%E6%97%A5
振替休日
振替休日は「国民の祝日の日曜日の翌日の月曜日以降の国民の祝日でない祝日の翌日」とちょっとややこしいですがそのように祝日法ではそう記されています。簡単に言えば日曜日が祝日なら次の平日が振替休日に充てられると考えた方がいいかもです。
後編では考え方や実際書いたソースコードを載せて解説とは言い難いですが、説明をしていこうかなと思います。今週中には上げるのでよろしくお願い致します。
参考文献
祝日法:https://www8.cao.go.jp/chosei/shukujitsu/gaiyou.html
国民の祝日:https://ja.wikipedia.org/wiki/%E5%9B%BD%E6%B0%91%E3%81%AE%E7%A5%9D%E6%97%A5
- 投稿日:2019-12-15T12:17:29+09:00
Bottom App BerにBottom Navigationぶち込んでみた
はじめに
こんにちはメッシーです。
今回はBottomAppBerを見た目に入れながらBottomNavigationによるFragmentの切り替えを実装します
BottomAppBerだけを使ってみたとか、BottomAppBerにボタンを配置してみたなどの記事はあったがBottomNavigationを追加してFragmentoの切り替えも加えてる記事がなかったのでこれを書くことにしまいした。BottomAppBerの詳細についても軽く紹介しながら行っていきます
最後にサンプルのgithubを載っけるのでそのまま使えますGradle設定
はじめにGradlにMaterialとNavigationを追加します。
記事によってはAPIのバージョンを指定しているとこがあったが特に気にしなくてもできた。app/build.gradledependencies { //Material implementation 'com.google.android.material:material:1.0.0-alpha1' //Navigation implementation 'androidx.navigation:navigation-fragment:2.0.0' implementation 'androidx.navigation:navigation-ui:2.0.0' }BottomAppBer作成
BottomAppBarはCoordinatorLayoutの子でなければなりません。
activity_main.xml<?xml version="1.0" encoding="utf-8"?> <androidx.coordinatorlayout.widget.CoordinatorLayout 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"> <com.google.android.material.bottomappbar.BottomAppBar android:id="@+id/bottomAppBar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:layout_gravity="bottom" app:backgroundTint="@color/colorAccent" app:fabAlignmentMode = "center" app:fabCradleRoundedCornerRadius="10dp" app:fabCradleMargin="5dp" app:titleTextColor="@android:color/black"> </com.google.android.material.bottomappbar.BottomAppBar> <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/fab" android:layout_width="80dp" android:layout_height="match_parent" android:backgroundTint="@color/colorPrimary" app:layout_anchor="@id/bottomAppBar" /> </androidx.coordinatorlayout.widget.CoordinatorLayout>属性
BottomAppBerのUIをいじることができます。
BottomAppBerの場所を変えられる
app:fabAlignmentMode = "center"
app:fabAlignmentMode = "end"BottomAppBerの隙間を変える
app:fabCradleMargin="数字dp"
BottomAppBerの角を丸くする
app:fabCradleRoundedCornerRadius="数字dp"
BottomAppBerのボタンの高さを調整する
app:fabCradleVerticalOffset = "数字dp"
下の参考にリンクを載せたので色々好きな形を試してみてください!
BottomNavigation作成
Fail→Naw→Activity→BottomNavigation
を選択して作成してください。実際に生成されるActivityは今回は必要ないですが、Menuなどの項目を自動で作ってくれるので便利です。
AndroidManifestの設定
今回使用するのはMainActivityなのでFailを作った際にAndroidManifestのActivityにBottomNavigationも追加されてしまうので消します。
消さないと落ちます。Menuの設定
デフォルトだと3つになっているますが今回は左右の2つでいいので1つ消します。
bottom_nav_menu.xml<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/navigation_home" android:icon="@drawable/ic_home_black_24dp" android:title="item1" /> <item android:id="@+id/navigation_notifications" android:icon="@drawable/ic_notifications_black_24dp" android:title="item2" /> </menu>XMLの設定
activity_mainに起動時に最初に開くFragmentを設定します。
activity_main.xml<?xml version="1.0" encoding="utf-8"?> <androidx.coordinatorlayout.widget.CoordinatorLayout //省略 > <fragment android:id="@+id/nav_host_fragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost="true" app:layout_constraintBottom_toTopOf="@id/nav_view" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:navGraph="@navigation/mobile_navigation" /> <com.google.android.material.bottomappbar.BottomAppBar //省略 > </com.google.android.material.bottomappbar.BottomAppBar> <com.google.android.material.floatingactionbutton.FloatingActionButton //省略 /> </androidx.coordinatorlayout.widget.CoordinatorLayout>Activityの設定
最初に開くFragmentの設定などを行います。
ここに書いたコードは主にBottomNavigationを作った際にできるActivityを元に作りました。MainActvity.ktclass MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val navView: BottomNavigationView = findViewById(R.id.bottomNavigationView) val navController = findNavController(R.id.nav_host_fragment) val appBarConfiguration = AppBarConfiguration( setOf( R.id.navigation_home, R.id.navigation_notifications ) ) setupActionBarWithNavController(navController, appBarConfiguration) navView.setupWithNavController(navController) } }完成です!
今回のgithubです
https://github.com/175atsu/BotannAppBarSample参考
属性のところを詳しく書いてあります。
- 投稿日:2019-12-15T12:17:29+09:00
BottomAppBarにBottomNavigationぶち込んでみた
はじめに
こんにちはメッシーです。
今回はBottomAppBarを見た目に入れながらBottomNavigationによるFragmentの切り替えを実装します
BottomAppBarだけを使ってみたとか、BottomAppBarにボタンを配置してみたなどの記事はあったがBottomNavigationを追加してFragmentoの切り替えも加えてる記事がなかったのでこれを書くことにしまいした。BottomAppBarの詳細についても軽く紹介しながら行っていきます
最後にサンプルのgithubを載っけるのでそのまま使えますGradle設定
はじめにGradlにMaterialとNavigationを追加します。
記事によってはAPIのバージョンを指定しているとこがあったが特に気にしなくてもできた。app/build.gradledependencies { //Material implementation 'com.google.android.material:material:1.0.0-alpha1' //Navigation implementation 'androidx.navigation:navigation-fragment:2.0.0' implementation 'androidx.navigation:navigation-ui:2.0.0' }BottomAppBar作成
BottomAppBarはCoordinatorLayoutの子でなければなりません。
activity_main.xml<?xml version="1.0" encoding="utf-8"?> <androidx.coordinatorlayout.widget.CoordinatorLayout 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"> <com.google.android.material.bottomappbar.BottomAppBar android:id="@+id/bottomAppBar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:layout_gravity="bottom" app:backgroundTint="@color/colorAccent" app:fabAlignmentMode = "center" app:fabCradleRoundedCornerRadius="10dp" app:fabCradleMargin="5dp" app:titleTextColor="@android:color/black"> </com.google.android.material.bottomappbar.BottomAppBar> <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/fab" android:layout_width="80dp" android:layout_height="match_parent" android:backgroundTint="@color/colorPrimary" app:layout_anchor="@id/bottomAppBar" /> </androidx.coordinatorlayout.widget.CoordinatorLayout>属性
BottomAppBarのUIをいじることができます。
BottomAppBarの場所を変えられる
app:fabAlignmentMode = "center"
app:fabAlignmentMode = "end"BottomAppBarの隙間を変える
app:fabCradleMargin="数字dp"
BottomAppBarの角を丸くする
app:fabCradleRoundedCornerRadius="数字dp"
BottomAppBarのボタンの高さを調整する
app:fabCradleVerticalOffset = "数字dp"
下の参考にリンクを載せたので色々好きな形を試してみてください!
BottomNavigation作成
Fail→Naw→Activity→BottomNavigation
を選択して作成してください。実際に生成されるActivityは今回は必要ないですが、Menuなどの項目を自動で作ってくれるので便利です。
AndroidManifestの設定
今回使用するのはMainActivityなのでFailを作った際にAndroidManifestのActivityにBottomNavigationも追加されてしまうので消します。
消さないと落ちます。Menuの設定
デフォルトだと3つになっているますが今回は左右の2つでいいので1つ消します。
bottom_nav_menu.xml<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/navigation_home" android:icon="@drawable/ic_home_black_24dp" android:title="item1" /> <item android:id="@+id/navigation_notifications" android:icon="@drawable/ic_notifications_black_24dp" android:title="item2" /> </menu>XMLの設定
activity_mainに起動時に最初に開くFragmentを設定します。
activity_main.xml<?xml version="1.0" encoding="utf-8"?> <androidx.coordinatorlayout.widget.CoordinatorLayout //省略 > <fragment android:id="@+id/nav_host_fragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost="true" app:layout_constraintBottom_toTopOf="@id/nav_view" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:navGraph="@navigation/mobile_navigation" /> <com.google.android.material.bottomappbar.BottomAppBar //省略 > </com.google.android.material.bottomappbar.BottomAppBar> <com.google.android.material.floatingactionbutton.FloatingActionButton //省略 /> </androidx.coordinatorlayout.widget.CoordinatorLayout>Activityの設定
最初に開くFragmentの設定などを行います。
ここに書いたコードは主にBottomNavigationを作った際にできるActivityを元に作りました。MainActvity.ktclass MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val navView: BottomNavigationView = findViewById(R.id.bottomNavigationView) val navController = findNavController(R.id.nav_host_fragment) val appBarConfiguration = AppBarConfiguration( setOf( R.id.navigation_home, R.id.navigation_notifications ) ) setupActionBarWithNavController(navController, appBarConfiguration) navView.setupWithNavController(navController) } }完成です!
今回のgithubです
https://github.com/175atsu/BotannAppBarSample参考
属性のところを詳しく書いてあります。
- 投稿日:2019-12-15T11:40:01+09:00
RetrofitのAPIレスポンスをモッキング
コードのテストをしたり、APIがまだ未実装だったりで、モックが欲しかったのでそのメモ。
導入
Retrofit Mockを使う。
def retrofit_version = '2.7.0' implementation "com.squareup.retrofit2:retrofit:$retrofit_version" implementation "com.squareup.retrofit2:retrofit-mock:$retrofit_version"実装
サービスインタフェースを実装したモックを作成し、そこに返したいデータを書く。
Service.ktinterface Service { @GET("api/v1/auth") fun getAuth(@Query("id") id: String): Single<Auth> @GET("api/v1/payment") fun getPayment(@Query("token") token: String): Single<List<Payment>> }MockService.ktclass MockService(private val delegate: BehaviorDelegate<Service>) : Service { override fun getAuth(id: String): Single<Auth> { val auth = Auth("xxx") return delegate.returningResponse(auth).getAuth(id) } override fun getPayment(token: String): Single<List<Payment>> { val payment = Payment(1000) return delegate.returningResponse(listOf(payment)).getPayment(token) } }あとはAPI生成時にモックを返すようにすればOK。
Api.ktclass Api { companion object { fun create(): Service { val retrofit = Retrofit.Builder() .baseUrl("https://mock.com/") .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(MoshiConverterFactory.create()) .build() val behavior = NetworkBehavior.create() val mockRetrofit = MockRetrofit.Builder(retrofit) .networkBehavior(behavior) .build() val delegate = mockRetrofit.create(Service::class.java) return MockService(delegate) } } }補足
モック生成時にデフォルト値が設定されている項目があるので、随時変更すること。
val behavior = NetworkBehavior.create() behavior.setDelay(100, TimeUnit.MILLISECONDS) behavior.setVariancePercent(0) behavior.setFailurePercent(0) behavior.setErrorPercent(0)
項目 内容 デフォルト値 Delay 発生する遅延 2000ms VariancePercent 発生する遅延の割合 ±40% FailurePercent 発生する失敗の割合 3% ErrorPercent 発生するエラーの割合 0% 作ったやつ
- 投稿日:2019-12-15T10:38:18+09:00
RxJavaで連続してAPIを叩いた時に発生したエラーのハンドリング
RxJavaで連続してAPIを叩いた時に発生したエラー、独自の形式にすることで処理を切り分けたいと思ったこと、ありませんか?僕はあります(そもそもAPIのエラーで判別したいけど、実装がアレだったもんで)。
NGパターン
チェインの途中で
doOnError
を挟んでエラーをthrowしたけど、止まらずその後のflatMap
に流れてしまうのでダメ。repo.getAuth("xxx") .doOnError { throw AuthError("${it.message}") } .flatMap { repo.getPayment(it.token) }OKパターン
onErrorResumeNext
を使うと、エラーが発生した時に別のObservableを流せる。ここで独自のエラーを返せば、onError
で処理を切り分けることが可能。repo.getAuth("xxx") .onErrorResumeNext { Single.error(AuthError("${it.message}")) } .flatMap { repo.getPayment(it.token) }参考
- 投稿日:2019-12-15T08:04:58+09:00
Flutterウィークリー #85
Flutterウィークリーとは?
FlutterファンによるFlutterファンのためのニュースレター
https://flutterweekly.net/この記事は#85の日本語訳です
https://mailchi.mp/flutterweekly/flutter-weekly-85※Google翻訳を使って自動翻訳を行っています。翻訳に問題がある箇所を発見しましたら編集リクエストを送っていただければ幸いです。
読み物&チュートリアル
Flutterオンラインおよびオフライン接続を実装する方法
https://alltechsavvy.com/online-offline-connectivity-in-flutter/
Sagar Shendeによるこのチュートリアルで、ユーザーがオンラインまたはオフラインのときにアプリ内の情報を処理する方法を学びます。
Flutter Webを使用してサーバーにファイルをアップロードする
https://rodolfohernan20.blogspot.com/2019/12/upload-files-to-server-with-flutter-web.html
Flutter Webアプリでファイルのアップロードを処理できるようにするための正確な手順。
Flutterサポートする真新しいDart Pad.dev
https://medium.com/dartlang/a-brand-new-dartpad-dev-with-flutter-support-16fe6027784
Dart PadはオンラインでFlutterサポートするようになりました!
EyeEm Flutter ed
https://medium.com/flutter-community/eyeem-fluttered-3ad12efc8b16
EyeEmのŁukaszWiśniewskiは、既存のAndroidおよびiOSネイティブアプリにFlutterを追加した後の調査結果について説明しています。
暗黙的なアニメーションによるFlutterアニメーションの基本
https://medium.com/flutter/flutter-animation-basics-with-implicit-animations-95db481c5916
Alethea K. Flowersがアニメーションの基本を紹介します。ビデオも利用できます。
Flutter RaisedButtonクックブック
https://medium.com/flutter-community/flutter-raisedbutton-cookbook-7c3d4a82b26f
RaisedButtonについて知りたいことはすべて、Aneesh Joseによって説明されました。
Flutter Firebaseダイナミックリンクの処理
https://medium.com/flutter-community/handling-firebase-dynamic-links-in-flutter-7c1de6a4e2e
Nikita Gandhiによるこの記事のおかげで、 FlutterでFirebase Dynamicリンクを最大限に活用できます。
Flutterパッケージにアセットを含める
https://medium.com/flutter-community/including-assets-in-a-flutter-package-dd4a82a38ca9
Flutterパッケージでアセットを適切に処理します。
Flutter Developerになるためのロードマップ(初心者向けリソース)
Flutterを始めた人のために、Ashish RawatはFlutterを学ぶための膨大なリソースのリストをFlutterます。
ビデオ&メディア
始まりFlutterウィジェットツリーを理解します
https://www.youtube.com/watch?v=Xu92WAlf0vI&feature=share
最適化の側面を含むウィジェットの基本と、それらを再利用するさまざまな方法の理解。
AnimatedBuilderとAnimatedWidgetを使用してカスタムの明示的なアニメーションを作成するFlutter in Focus
https://www.youtube.com/watch?v=fneC7t4R_B0&t=174s
このFlutter in Focusのエピソードでは、エミリー・フォーチュナが、AnimatedBuilderまたはAnimatedWidgetを他のアニメーションウィジェットと比較して使用する理由を紹介しています。
Flutterクロックコンテスト-プロトタイプクロックの構築-ライブストリーム
https://www.youtube.com/watch?v=HYrbFTZ88nY
Flutter Clockコンテストに参加する場合、このビデオは完全なクロック作成プロセスのライブストリームです。
REST APIとの対話
https://www.youtube.com/watch?v=ZMNp9Ev6cl0
httpパッケージを使用してDart Flutter REST APIにGETリクエストを行う方法を学びます。
スーパー列挙? Dart & Flutterチュートリアル-カスタムデータストア
https://www.youtube.com/watch?v=iS_05wRScic
super_enumパッケージを使用してDartで列挙型を使用する方法。
トグルボタン(今週のFlutterウィジェット)
https://www.youtube.com/watch?v=kVEguaQWGAY&list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG&index=64
ユーザーに関連する多数のオプションから選択を許可しますか? ToggleButtonsをアプリに追加すると役立ちます。
ライブラリ&コード
GitHub-apgapg / flutter_physics_concepts
https://github.com/apgapg/flutter_physics_concepts
視覚物理学学習アプリのソースコード。
GitHub-flutterigniter / flutter_data_stream_builder
https://github.com/flutterigniter/flutter_data_stream_builder
実用的なデフォルトを備えた実用的なStreamBuilder
GitHub-JonathanMonga / flutter_trianglify
https://github.com/JonathanMonga/flutter_trianglify
AndroidおよびiOS向けの美しい三角形アートビューを生成するFlutterライブラリ。
GitHub-Milad-Akarie / auto_route_library
https://github.com/Milad-Akarie/auto_route_library
AutoRouteは、ナビゲーションに必要なすべてのものが自動的に生成されるルート生成ライブラリです。
GitHub-rodydavis / easy_google_maps
https://github.com/rodydavis/easy_google_maps
WebおよびモバイルでのFlutter用の簡単なGoogleマップ
GitHub-yako-dev / flutter-settings-ui
https://github.com/yako-dev/flutter-settings-ui
Flutterアプリのネイティブ設定画面を数分で作成します。
GitHub-yako-dev / flutter-status-alert
https://github.com/yako-dev/flutter-status-alert
Appleシステムのような自己非表示ステータスアラートを表示します。ユーザーフローを中断することなくユーザーに通知するのに適しています。
- 投稿日:2019-12-15T08:03:31+09:00
Flutterウィークリー #84
Flutterウィークリーとは?
FlutterファンによるFlutterファンのためのニュースレター
https://flutterweekly.net/この記事は#84の日本語訳です
https://mailchi.mp/flutterweekly/flutter-weekly-84※Google翻訳を使って自動翻訳を行っています。翻訳に問題がある箇所を発見しましたら編集リクエストを送っていただければ幸いです。
読み物&チュートリアル
FlutterモジュールをネイティブのAndroidプロジェクトに追加し、Codemagicでテストする方法
https://blog.codemagic.io/flutter-module-android-yaml/
Flutterモジュールを既存のAndroidアプリに統合する方法に関するSouvik Biswaによるチュートリアル。
Flutterアプリケーションで複数のテーマを管理する
https://blog.dammak.dev/managing-multiple-themes-in-flutter-application-ck3396mer01542as1rxv7xx5r
Damola Adekoyaは、アプリケーションにさまざまなテーマを実装する方法を示します。
Flutter BLoCの使用開始
https://dev.to/netguru/getting-started-with-flutter-bloc-1pkm
Kacper Kogutによるこの記事でBLoCを適切に使用する方法を学ぶ
FlutterアプリでGoogleマップにカスタムマーカーを作成する
https://infinum.com/the-capsized-eight/creating-custom-markers-on-google-maps-in-flutter-apps
JosipKrnjićが、 Flutter Googleマップにカスタムメイドマークを追加する方法について詳しく説明します。
電子ブック-簡潔にFlutter
https://www.syncfusion.com/ebooks/flutter-succinctly
Flutter Ed Freitasによる無料の電子ブック
Flutterアプリの広告
https://medium.com/@greg.perry/ads-in-your-flutter-app-16ad82ce698a
Greg Perryによるこの記事で、アプリに広告を挿入してプロジェクトを収益化する方法を学びましょう
Flutter Cameraプラグインの探索
https://medium.com/@divyanshub024/exploring-flutter-camera-plugin-d2c54ac95f05
Divyanshu BhargavaはFlutterのプラグインカメラを詳細に分析します。
Flutterアラートダイアログからカスタムダイアログ
https://medium.com/flutterpub/flutter-alert-dialog-to-custom-dialog-966195157da8
Ishan Fernandoによるこの記事のおかげで、 Flutterでダイアログを作成する方法を学びます。
Flutter for Web:ポートフォリオWebサイトの構築
https://medium.com/flutter-community/flutter-for-web-building-a-portfolio-website-3e9865710efe
Flutterを使用してポートフォリオWebサイトを作成しますか? Aditya Gurjarは、必要なすべてのステップを示します。
Flutter Web + Netlify:2分で適切な方法で継続的に展開
私はNetlifyの大ファンであり、Netlifyを頻繁に使用しています。 Shahrukh Siddiquiが、NetlifyでFlutter Webサイトを簡単に展開する方法についての記事を書きました。
Flutter Webのレイアウトテンプレートと基本ナビゲーション
Dane Mackierは、Webプロジェクトのテンプレートを紹介します。
モジュラーFlutterアプリ—設計と考慮事項
https://medium.com/flutter-community/modular-flutter-apps-design-and-considerations-59c5ac65352
アプリのモジュール化の長所と短所、およびGonçaloPalmaによるAndroidのモジュール化との違いに関するレビュー。
ファイル名を指定して実行Flutter GitHubのアクションのドライバーテスト- Flutterコミュニティ
https://medium.com/flutter-community/run-flutter-driver-tests-on-github-actions-13c639c7e4ab
Katarina Sheremetは、Githubアクションを使用してFlutterテストを実行する方法を説明します。
あなたの声を聞く— 2019年第3四半期のFlutterユーザー調査からの教訓
https://medium.com/flutter/we-hear-you-learnings-from-q3-2019-flutter-user-survey-af588dbd71b1
別の四半期がFlutterすると、別のFlutter調査結果がFlutterチームによって共有されます。
FlutterでAPIを操作する
https://medium.com/flutter-community/working-with-apis-in-flutter-8745968103e9
FlutterからFlutterへのアクセスに問題がありますか? Pooja Bhaumikは、必要なすべての部品について説明します。
ビデオ&メディア
Flutter Frameworkを使用してモバイルプログラマになるための22の短いレッスン
https://dev.to/zaiste/22-short-lessons-to-become-a-mobile-programmer-using-flutter-framework-d9j
Flutterさまざまな側面を示すためにザイステによって作成された膨大なビデオのリスト。
ColorFiltered( Flutter Widget of the Week)
https://www.youtube.com/watch?v=F7Cll22Dno8&list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG&index=62
ColorFilteredウィジェットをチェックしてください。これにより、奇妙で素晴らしい方法で色を変更およびモーフィングできます。
Flutter Generated Dependency Injection – Kiwiチュートリアル
https://www.youtube.com/watch?v=1CHicTngyZY&feature=youtu.be
このチュートリアルでは、コード生成のパワーとkiwiパッケージを活用して、すぐに依存関係注入コンテナーを構成する方法を学習します。
最新バージョンのflutterとdartをraspberry pi 4にインストールし、アプリケーションを作成する
https://www.youtube.com/watch?v=SHc3NB1LdlI&feature=youtu.be
ラズベリーパイにフラッターをインストールし、フラッターアプリを作成する方法に関する4分間のチュートリアル。
Flutterバイナリクロックを作成してバイナリを学ぶ
https://www.youtube.com/watch?v=VkTj1U_exwA&feature=youtu.be
バイナリの仕組みを知っていますか? Flutterバイナリクロックを構築して、基本を学びます。 2020年1月22日の競技期限前に独自の時計を作成します。
ナビゲータ付きリストビュー| Flutterチュートリアル
https://www.youtube.com/watch?v=OpMyn7SdAWo
2番目の画面に移動して、 FlutterでListViewを作成する方法を学びましょう!
組み込みの明示的なアニメーションを使用して最初の方向アニメーションを作成する
Flutter in Focusのこのエピソードでは、Andrew Fitz GibbonがFlutter明示的なアニメーションを紹介しています。
TweenAnimationBuilderを使用して独自のカスタム暗黙アニメーションを作成する
https://www.youtube.com/watch?v=6KiPEqzJIKQ&list=PLjxrf2q8roU2HdJQDjJzOeO6J3FoFLWr2&index=20
このエピソードではFlutterフォーカスで、エミリー・フォーチュナショーはどのようにTweenAnimationBuilderを使用するFlutterアプリの基本的なアニメーションを構築します。
FlutterとFCMを使用したスマートプッシュ通知
https://www.youtube.com/watch?v=2TSm2YGBT1s&feature=youtu.be
Firebase Cloud Messaging(FCM)を使用してFlutterプッシュ通知を単一のデバイス、トピック、またはユーザーセグメントに送信します。
パフォーマンスの問題についてFlutterアプリケーションのプロファイルを作成するツールとテクニック
https://www.youtube.com/watch?v=GL61CotxCmM
このビデオでは、コードの潜在的なパフォーマンスの問題を発見するために、 Flutterアプリケーションのプロファイルを作成するためのいくつかのツールと手法について説明します。
ライブラリ&コード
GitHub-dreamsoftin / flutter_wordpress
https://github.com/dreamsoftin/flutter_wordpress
Flutter WordPress API
Flutterガイド
https://github.com/devonfw-forge/devonfw4flutter
Flutter基本とクリーンで構造化されたFlutter開発のギャップを埋めることを目的としたガイド。
faob-dev / flutter_circular_text
https://github.com/faob-dev/flutter_circular_text
Flutter円形テキストウィジェット
GitHub-ganeshsp1 / Flutter Bluetooth_PCControl
https://github.com/ganeshsp1/Flutter_Bluetooth_PC_Control
Bluetoothを使用してPCを制御するFlutterアプリ
GitHub-モジュロ値/ flutter_audio_wav_demo
https://github.com/modulovalue/flutter_audio_wav_demo
Dart .wavファイルを作成し、 Flutter視覚化します。
GitHub-rxlabz / boat_heroes
https://github.com/rxlabz/boat_heroes
Flutterヒーロートランジションの例
- 投稿日:2019-12-15T00:37:46+09:00
Xamarin.Forms でガワネイティブアプリを作るときのテンプレートプロジェクトを作る1
今年は専ら Angular で Webアプリを作ったり、ガワネイティブアプリを作ったりしていますが Xamarin のアドベントカレンダーと聞いてやってきました。
概要
最近は、モバイルネイティブアプリよりも Webアプリ(SPA/PWA)、そしてそれを利用したガワネイティブアプリを推している私ですが、ガワネイティブアプリを作る時の「ガワ」には Xamarin(Xamarin.Forms) を採用しています。
なぜガワネイティブなのか?ネイティブではなく、Web(PWA)でもないのか?については、
のエントリがよく解説されていますのでご一読を。私のケースは BtoC ではなく BtoB であるため、全面的に一致するわけではありませんが、内容については大いに同意できます。
ガワネイティブアプリは、Webアプリがネイティブの機能を欲するから採用されるわけで、それを制御するためにWebアプリとネイティブ機能の相互通信が必要になります。
また、「ガワ」は
WebView
なわけですが、それがアプリとして自然に振る舞うために、いくつかの「設定」をしてあげる必要があります。この記事は、そのような「Xamarin(.Forms) でガワネイティブアプリを作るときのリファレンス」になればよいなと思って書きます。
尚、
Advent Calendar の締め切りに間に合わせるために意外と情報量が多かったので、何回かに分けます。
今回は初回です。目次
- 日本語入力時の画面高さの調整
- ステータスバー、あるいは SafeArea(ノッチ部)の色
- 【次回以降予定】アプリ情報の Web 側への引き渡し
- 【次回以降予定】
<input type="xxx">
への対応- 【次回以降予定】Back ボタンハンドリングの Web 側への移譲
- 【次回以降予定】スプラッシュスクリーンおよび初回読み込み時の対応
1. 日本語入力時の画面高さの調整
ソフトウェアキーボードが、コンテンツの手前が重なってしまう問題の解決です。
これは、
を適用して解決します。
Android の場合
今回のように WebView だけ対応すればよい場合は、
MainActivity.cs
に `.UseWindowSoftInputModeAdjust()
の行を追加してあげればよいみたいです。// MainActivity.cs protected override void OnCreate(Bundle bundle) { TabLayoutResource = Resource.Layout.Tabbar; ToolbarResource = Resource.Layout.Toolbar; base.OnCreate(bundle); global::Xamarin.Forms.Forms.Init(this, bundle); LoadApplication(new App()); Xamarin.Forms.Application.Current.On<Xamarin.Forms.PlatformConfiguration.Android>() .UseWindowSoftInputModeAdjust(WindowSoftInputModeAdjust.Resize); // ←ここ!! }iOS の場合
iOS の場合、前出のわたしのエントリを改善して頂いた、
があるのですが、WebView だけ対応すればよい場合、特にやることは無いですw
対応結果
これらの対応を行うと、下図のようにソフトウェアキーボードを表示していても、WebView のコンテンツが隠れることはなくなります。
なお、この例では Materialize-CSS の Starter Template を表示させています。
元コンテンツ 対応前 対応後(Android) 対応後(iOS) 2. ステータスバー、あるいは SafeArea(ノッチ部)の色
Android と iOS のステータスバーの色は、Webアプリのテーマ色に合わせたいものです。
また、iOS では SafeArea(ノッチのところ)を除けるような対応が必要になります。ガワのプロジェクト作成直後は下図のように、Android では青系のステータスバーに、iOS では白色になってしまいます。
対応前(Android) 対応前(iOS) この例では Web アプリのテーマ色が緑なので、どちらもステータスバーを緑色にします。
Android の場合
Android プロジェクトにある
Resources/values/styles.xml
のcolorPrimaryDark
の色を修正します。下の例では#66BB6A
に書き換えました。(colorPrimary
も変えておいた方が良いかもしれませんね。)Resources/values/styles.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <style name="MainTheme" parent="MainTheme.Base"> </style> <!-- Base theme applied no matter what API --> <style name="MainTheme.Base" parent="Theme.AppCompat.Light.DarkActionBar"> <!--If you are using revision 22.1 please use just windowNoTitle. Without android:--> <item name="windowNoTitle">true</item> <!--We will be using the toolbar so no need to show ActionBar--> <item name="windowActionBar">false</item> <!-- Set theme colors from https://aka.ms/material-colors --> <!-- colorPrimary is used for the default action bar background --> <item name="colorPrimary">#2196F3</item> <!-- colorPrimaryDark is used for the status bar --> <item name="colorPrimaryDark">#66BB6A</item> ←ここ!!! <!-- colorAccent is used as the default value for colorControlActivated which is used to tint widgets --> <item name="colorAccent">#FF4081</item> <!-- You can also set colorControlNormal, colorControlActivated colorControlHighlight and colorSwitchThumbNormal. --> <item name="windowActionModeOverlay">true</item> <item name="android:datePickerDialogTheme">@style/AppCompatDialogStyle</item> </style> <style name="AppCompatDialogStyle" parent="Theme.AppCompat.Light.Dialog"> <item name="colorAccent">#FF4081</item> </style> </resources>iOS の場合
ノッチの対応がちょっと面倒です。
こちらの方法をとります。ノッチ(SafeArea)の領域分のパディングを設ける Effects を作って、Page に適用する方法です。
まずは、Forms の共通プロジェクトに
SafeAreaPaddingEffect
を作成します。SafeAreaPaddingEffect.cs
using System; using Xamarin.Forms; namespace GawaNativeGettingStarted { public class SafeAreaPaddingEffect : RoutingEffect { public SafeAreaPaddingEffect() : base("GawaNativeGettingStarted.SafeAreaPaddingEffect") { } } }先に
MainPage.xaml
に適用しちゃいましょう。
尚、BackgroundColor="#66BB6A"
で指定した色が、Web アプリ側の緑のテーマ色です。MainPage.xaml
<?xml version="1.0" encoding="utf-8"?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:d="http://xamarin.com/schemas/2014/forms/design" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" xmlns:local="clr-namespace:GawaNativeGettingStarted;assembly=GawaNativeGettingStarted" xmlns:effect="clr-namespace:GawaNativeGettingStarted" x:Class="GawaNativeGettingStarted.MainPage"> <StackLayout Orientation="Vertical" BackgroundColor="#66BB6A"> <StackLayout.Effects> <effect:SafeAreaPaddingEffect /> </StackLayout.Effects> <WebView HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" Source="https://eeaab7d6.ngrok.io"/> </StackLayout> </ContentPage>次に iOS のプロジェクトにも
SafeAreaPaddingEffect
を作成し、SafeArea 考慮の実装をします。using GawaNativeGettingStarted.iOS.Effects; using UIKit; using Xamarin.Forms; using Xamarin.Forms.Platform.iOS; [assembly: ResolutionGroupName("GawaNativeGettingStarted")] [assembly: ExportEffect(typeof(SafeAreaPaddingEffect), nameof(SafeAreaPaddingEffect))] namespace GawaNativeGettingStarted.iOS.Effects { class SafeAreaPaddingEffect : PlatformEffect { Thickness _padding; protected override void OnAttached() { if (Element is Layout element) { if (UIDevice.CurrentDevice.CheckSystemVersion(11, 0)) { _padding = element.Padding; var insets = UIApplication.SharedApplication.Windows[0].SafeAreaInsets; // Can't use KeyWindow this early if (insets.Top > 0) // We have a notch { element.Padding = new Thickness(_padding.Left + insets.Left, _padding.Top + insets.Top, _padding.Right + insets.Right, _padding.Bottom); return; } } // Uses a default Padding of 20. Could use an property to modify if you wanted. element.Padding = new Thickness(_padding.Left, _padding.Top + 20, _padding.Right, _padding.Bottom); } } protected override void OnDetached() { if (Element is Layout element) { element.Padding = _padding; } } } }これは前出のリンク先の内容そのものです。
ここまでの対応で、SafeArea 分の余白を設け、背景色を Web アプリ側に合わせることができました。
対応中(iOS) しかし、ステータスバーの文字色が黒になっています。これを白にしましょう。
参考になるエントリはこちら↓です。これを参考に iOS プロジェクトの
info.plist
を編集し、次のエントリを追加します。
- Status bar style : White
- View controller-based status bar appearance : No
これで、ステータスバーの文字色が白になります。
対応結果
対応後の見た目はこんな感じになります。結構ネイティブアプリっぽくなって来たでしょう?
対応後(Android) 対応後(iOS) まとめ
とりあえず初回ということで、見た目中心の対応内容を挙げてみました。
一通り揃ったら、GitHub にサンプルを上げて、あわよくば可能なところは nuget パッケージに切り出して利用できるようにしていきたいと思います。見た目の対応でも、ダークテーマ対応などはまったく無知なのでこれから勉強していきます。
次回エントリは、、、年内いける・・・かな?参考(次回以降のも含む)
- 投稿日:2019-12-15T00:19:16+09:00
androidでfortran開発する
概要
androidでfortranコードを書いて実行する。
スタック
CUI環境はUserLAndを使った。Termuxというものもあるが、昔試したところfortranコンパイラが使えなかった。UserLAndではLinuxパッケージもUbuntuやArch、kaliなどから選ぶことができ、Ubuntuの場合はaptでgfortranが入る。
エディタは別にEmacsでなくてもvimでもnanoでもお好きなものをどうぞ。
方法
UserLAndのインストール
完全に焼き直しで申し訳ないが、下記の記事を参照してほしい。
https://qiita.com/bluepost59/items/7f5608dff98c82ffb668
Google Playからアプリをインストールして、環境を作る。結構でかいダウンロードが走るので、回線を確認してから環境構築に入ること。時間は5分〜10分程度かかる。今回はUbuntuを使った。
うまく行けば下記のようにCUIが立ち上がる。ソフトキーボードだと画面が半分圧迫されるので、ハードウェアキーボードを使うほうがよい。百均でOTGケーブルを買ってくればUSBキーボードを使える。自分はbluetoothキーボードを使ったが快適に使える。
aptで環境構築する
aptでgfortranをインストールする。ちゃんと
apt update
しないとインストールでNot Foundエラーが出る。apt update && apt install gfortran emacsプログラミング
あとは通常のCUIプログラミングと一緒
無事hello worldが実行できました。
- 投稿日:2019-12-15T00:09:07+09:00
FirebaseとUnityの連携 入門(Cloud Storage編)
この記事は、Firebase Advent Calendar 2019の15日目の記事です。
概要
以前、『FirebaseとUnityでアプリ開発(ハンズオンみたいなやつ)』という記事をアップし、そこでFirebaseとUnityの連携方法を簡単に解説しました。
今回は特定のFirebaseのサービスを扱う際、まずは0から作るのではなく、サンプルを活用してFirebaseとUnityの連携をいち早く体験できる方法のご紹介です。
Unityの場合、AssetBundleをサーバに配置し、
UnityWebRequest.Get
などを使ってAssetBundleデータをダウンロードしますよね。
そのため今回は、Cloud Storageを触ってみることで、ゆくゆくAssetBundleデータをFirebaseで管理する設計イメージができないか、まずはやってみましょう。環境
- MacBookPro Mojave 10.14
- Unity 2019.2.9f1
- Firebase for Unity 6.3.0
セットアップ
まずは、導入方法を参考にFirebaseとUnityの連携準備は済ませておきましょう。
そしてサンプルコードとして公式が用意しているfirebase/quickstart-unityのCloud Storage for Firebase Quickstartを活用します。
一見、FirebaseもUnityも古いバージョンで作られていますが、大幅な変更がない最低限の機能はちゃんと動くので、本格的なアプリへの導入の際の設計・開発で参考にしていきましょう。
Unity側の調整
ですが、活用すると言ってもquickstart-unity/storage/testapp/Assets/Firebase/Sample/Storage/UIHandler.cs
だけ扱うので、クラス名だけ変えてC#スクリプトを作成し、コピペしましょう。その後、コピペして作ったC#を空のGameObjectにアタッチし、そのInspector上に表示されるGUISkin変数にGUISkinを作成してアタッチしましょう。
また、カメラの調整を
Skybox
からSolid Color
に変更して、サンプルが分かりやすいように調整しておきましょう。Cloud Storageの設定
次にFirebaseコンソール側の作業になります。
Storageのメニューを開き、事前に画像などをアップし、詳細上から画像のリンクをコピーなどしてメモしておきましょう。
次にルールの設定です。
公式の『Storage セキュリティ ルールを使ってみる』に各ルールの設定サンプルがあり、Authを扱わないので今回は公開のルールを扱います。
この公開のルールは、誰でも読み込みと書き込みが可能な設定なので作業終了後に設定を戻しておくようにしておきましょう(自己責任でお願いします?)。実行
Local File Path
とStorage Location
の設定を先ほどメモした内容に書き換えてDownload Bytes
/Download Stream
/Download to File
の各ボタンを押してみると以下のようになります。成功せず、もしパーミッションエラーで403がある場合は、おそらくルールの設定変更忘れだと思います。
また、Local File Path
の変更を忘れているとデフォルトで設定されているdownloaded_file.txt
の名前でファイルが生成されてしまいます。さいごに
あとはAssetBundleさえ準備できればいつでもFirebaseで管理できるようになりそうですね。
ちなみにCloud Storageのファイルサイズには上りと下りで制限があるのか気になりましたでしょうか。
『Storage セキュリティ ルールを使ってみる』を読んでいるとルール側でデータサイズを指定して上り下りの制御ができるようです。ちょっと調べてみたところデータサイズの制限は特にないようなので、普通にAssetBundleのサーバとして扱えそうですよね^^