20191215のAndroidに関する記事は16件です。

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/lich

feature-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には対応していない、難しそう)

まとめ

こういう記事書くなら(当初そんなに書こうと思ってなくて..)予め写真もっと撮影しておけば良かったです。

とはいえ、初めて参加したイベントでしたが、自分の業務や興味とも関連していて満足度高かったです!
場所が大学だったので、懐かしい気持ちになれました!

今年のまとめを聞いてましたが、もう追い切れないくらい知りたいこと、興味あることが多いですね。
イベントでその道のトップクラスの人から知見や情報を一気に得ることが出来たのは、
このイベントに参加して良かったと思いました。

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

[Android] Kotlinのラムダ式でonClickListenerとonLongClickListenerを両立

Androidのアプリにてボタンなどを長押しする時の処理とタップしたときの処理を変えたい場合があります。

だいぶKotlinにも慣れてきて、ボタンなどのonClickListenerもラムダ式で書くようにしていますが、表題の通り、Kotlinのラムダ式を使ってonClickListeneronLongClickListenerを両立するのに躓いたのでメモしておきます。

onClickイベントは通常returnを持ちませんが、onLongClickイベントはBoolean型の戻り値を持ちます。この戻り値をtrueとするとonClickonLongClickが干渉しなくなります(公式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

の省略形のようですね。

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

【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.kt
package 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と―が入ってしまう

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

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内マイクロサービスアーキテクチャの説明

例えばこんな構成が作れます

スクリーンショット 2019-12-14 14.47.28.png

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を拡張する方法は使用できません。
MessengerBundleを詰めてやり取りします。
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で呼び出すときに、呼び出し先のapplicationIdclassNameが必要になります。
つまり、なんらかの方法でHost AppはPlugin Appの上記2つの情報を知る必要があります。
そして、その方法は変態向け: 端末内にインストールされているアプリのスキャン方法で解説します。

val intent = Intent().also {
    it.setClassName(applicationId, className)
}

変態向け: 端末内にインストールされているアプリのスキャン方法

ここから先はほとんど使う人が居なさそうな機能の紹介です。

先ほど、Host AppはPlugin AppのapplicationIdclassNameを知っていなければならない。という話をしました。
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を作り、それを外部に公開したとする。
そして、そのApplicationIdclassNameを公開したとする。
これらの情報が蓄積されれば、互いに互いのServiceを叩き合うMicroserviceの誕生!!かっこいい!!

(セキュリティ的に大丈夫なんかとか、依存したいアプリが入っているかわからない問題が課題。)

何か使える案下さい

面白そうな機能を見つけたので、変な構想を練って見ましたが、何に使えるのかイメージが湧きません・・・
一番のネックは、ユーザがアプリを入れてくれるとは限らない点ですね。
そこさえ克服できれば、アプリ間をService同士が連携し合う、Android内Microserviceアーキテクチャが陽の目を見る日も近い!

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

UnityでAndroid実機で実行したときに一部環境で画面が乱れる問題と対処法

発生した問題

UnityでAndroidスマートフォン向けの2Dゲームを開発していてゲーム画面のアスペクト比を統一させようとした。
方法はスクリプトでスマホ画面のアスペクト比が基準となるアスペクト比(今回は16:9)と異なる場合、ゲーム開始時にメインカメラのrectを変更してゲーム画面のアスペクト比を統一して余ったところは黒帯を表示させることで対応した。
エディタ上でゲームを実行したところ下の写真のようにカメラ外の左右の余った部分は黒帯が表示されている。
New Unity Project - test - Android - Unity 2019.2.13f1 Personal _DX11_ 2019_12_15 14_30_54.png
しかし、ビルドして手元のPixel 3aで実行したところ、下の写真のように左右のカメラの表示外の部分が黒帯にならず表示が乱れる現象が発生した。
Screenshot_20191215-142906.png
他の実機端末で実行するとエディタ上で実行したように、余った部分は黒く表示されて同じ現象は発生しなかった。

原因

調べたところ下の記事に書いてあるようにカメラの描写外の部分はメモリ上のゴミが残されているかららしい。
Android の特定機種で画面が乱れたら……

対処法

画面全体を映す二つ目のカメラを用意してメインカメラの描写外を表示させることによって一番下の画像のように問題が起こってた端末でもエディタ上と同じく表示されるようになった。全体を映す二つ目のカメラの描写の上にメインカメラを描写してるイメージ。二つ目のカメラはCulling Maskをすべてのレイヤーのオブジェクトを表示しない設定にして黒帯の部分にオブジェクトが映るのを回避している。二つ目のカメラのDepthをメインカメラより小さくしないと二つ目のカメラが上に表示されて全体真っ黒な画面になるので注意。(今回の場合だとBackgroundの色を変えることでほかの色の帯にすることが可能)
ゲームでどのようにカメラを使用しているかによって対処法も変わりそう。
New Unity Project - test - Android - Unity 2019.2.13f1 Personal _DX11_ 2019_12_15 14_32_33.png

Screenshot_20191215-143323.png

さいごに

もし間違えている箇所などがありましたらコメント等で教えてください

参考

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

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キーを作成してください。

  1. GCPコンソールにいく
  2. gcpプロジェクト作成
  3. 認証情報に行き、API key生成
  4. 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」の自動生成だと、シドニーが指定されています。

image.png

地図を表示する

地図を表示する方法を具体的に見ていきましょう。(「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.kt
class 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.kt
override fun onMapReady(googleMap: GoogleMap) {

        // style
        val mapOptions = MapStyleOptions.loadRawResourceStyle(this, R.raw.style_map)
        googleMap.setMapStyle(mapOptions)

    }

image.png

マーカーを設置する

マーカーの設置は、緯度経度やタイトル、タップしたときの情報などを指定してセットすることができます。

MainActivity.kt
val 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))

image.png

円を表示する

円を描画することもできます。

MainActivity.kt
mMap.addCircle(
        CircleOptions()
            .center(skytree)  // 中心の緯度経度
            .radius(500.0)  // 円の半径
            .strokeColor(0x500000FF)  // 境界線の色
            .strokeWidth(5f)  // 境界線の太さ
            .fillColor(0x0F0000FF)  // 円内部の色
    )

image.png

カメラを動かす

パッと切り替える方法と、アニメーションしながら切り替える方法があります。

MainActivity.kt
move_button.setOnClickListener {
    mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(tokyoTower, 15f))
}

move_to_tokyo_tower.gif

MainActivity.kt
animate_button.setOnClickListener {
    mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(tokyoTower, 15f))
}

animate_to_tokyo_tower.gif

マーカーが収まるように表示する

複数のマーカーが地図に収まるように表示したい時があるかと思います。

MainActivity.kt
override 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 で呼び出すようにしましょう。

image.png

その他

  • mapFragment.getMapAsync(this)onCreate で呼ぶようにしましう。onResumeなどでよぶと、地図が再生成されてしまうので、表示に時間がかかります。

まとめ

Maps SDKを使えば、簡単に地図を表示することがわかったかなとおもいます。良い地図ライフを。

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

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

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

Bottom App BerにBottom Navigationぶち込んでみた

はじめに

こんにちはメッシーです。

今回はBottomAppBerを見た目に入れながらBottomNavigationによるFragmentの切り替えを実装します
BottomAppBerだけを使ってみたとか、BottomAppBerにボタンを配置してみたなどの記事はあったがBottomNavigationを追加してFragmentoの切り替えも加えてる記事がなかったのでこれを書くことにしまいした。

BottomAppBerの詳細についても軽く紹介しながら行っていきます
最後にサンプルのgithubを載っけるのでそのまま使えます

今回の完成品がこちら
393794.jpg

Gradle設定

はじめにGradlにMaterialとNavigationを追加します。
記事によってはAPIのバージョンを指定しているとこがあったが特に気にしなくてもできた。

app/build.gradle
dependencies {
    //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>

393793.jpg

属性

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.kt
class 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

参考

属性のところを詳しく書いてあります。

https://medium.com/material-design-in-action/implementing-bottomappbar-material-components-for-android-f490c4a01708

http://tech.furyu.jp/blog/?p=6352

https://medium.com/over-engineering/hands-on-with-material-components-for-android-bottom-app-bar-28835a1feb82

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

BottomAppBarにBottomNavigationぶち込んでみた

はじめに

こんにちはメッシーです。

今回はBottomAppBarを見た目に入れながらBottomNavigationによるFragmentの切り替えを実装します
BottomAppBarだけを使ってみたとか、BottomAppBarにボタンを配置してみたなどの記事はあったがBottomNavigationを追加してFragmentoの切り替えも加えてる記事がなかったのでこれを書くことにしまいした。

BottomAppBarの詳細についても軽く紹介しながら行っていきます
最後にサンプルのgithubを載っけるのでそのまま使えます

今回の完成品がこちら
393794.jpg

Gradle設定

はじめにGradlにMaterialとNavigationを追加します。
記事によってはAPIのバージョンを指定しているとこがあったが特に気にしなくてもできた。

app/build.gradle
dependencies {
    //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>

393793.jpg

属性

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.kt
class 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

参考

属性のところを詳しく書いてあります。

https://medium.com/material-design-in-action/implementing-bottomappbar-material-components-for-android-f490c4a01708

http://tech.furyu.jp/blog/?p=6352

https://medium.com/over-engineering/hands-on-with-material-components-for-android-bottom-app-bar-28835a1feb82

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

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.kt
interface 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.kt
class 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.kt
class 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%

作ったやつ

demo-retro-mock

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

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

参考

RxJava error handling in chain API calls

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

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になるためのロードマップ(初心者向けリソース)

https://medium.com/flutterdevs/roadmap-to-become-a-flutter-developer-resources-for-beginners-ccb68718c84b

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システムのような自己非表示ステータスアラートを表示します。ユーザーフローを中断することなくユーザーに通知するのに適しています。

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

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分で適切な方法で継続的に展開

https://medium.com/@D10100111001/flutter-web-netlify-continuous-deployment-the-right-way-in-2-minutes-f2ed4a4fcbf7

私はNetlifyの大ファンであり、Netlifyを頻繁に使用しています。 Shahrukh Siddiquiが、NetlifyでFlutter Webサイトを簡単に展開する方法についての記事を書きました。

Flutter Webのレイアウトテンプレートと基本ナビゲーション

https://medium.com/flutter-community/layout-templates-and-basic-navigation-in-flutter-web-2e283edd5204

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を作成する方法を学びましょう!

組み込みの明示的なアニメーションを使用して最初の方向アニメーションを作成する

https://www.youtube.com/watch?v=CunyH6unILQ&list=PLjxrf2q8roU2HdJQDjJzOeO6J3FoFLWr2&index=22&t=33s&linkId=77691773

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ヒーロートランジションの例

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

Xamarin.Forms でガワネイティブアプリを作るときのテンプレートプロジェクトを作る1

今年は専ら Angular で Webアプリを作ったり、ガワネイティブアプリを作ったりしていますが Xamarin のアドベントカレンダーと聞いてやってきました。

概要

最近は、モバイルネイティブアプリよりも Webアプリ(SPA/PWA)、そしてそれを利用したガワネイティブアプリを推している私ですが、ガワネイティブアプリを作る時の「ガワ」には Xamarin(Xamarin.Forms) を採用しています。

なぜガワネイティブなのか?ネイティブではなく、Web(PWA)でもないのか?については、

のエントリがよく解説されていますのでご一読を。私のケースは BtoC ではなく BtoB であるため、全面的に一致するわけではありませんが、内容については大いに同意できます。

ガワネイティブアプリは、Webアプリがネイティブの機能を欲するから採用されるわけで、それを制御するためにWebアプリとネイティブ機能の相互通信が必要になります。

また、「ガワ」は WebView なわけですが、それがアプリとして自然に振る舞うために、いくつかの「設定」をしてあげる必要があります。

この記事は、そのような「Xamarin(.Forms) でガワネイティブアプリを作るときのリファレンス」になればよいなと思って書きます。

尚、Advent Calendar の締め切りに間に合わせるために 意外と情報量が多かったので、何回かに分けます。
今回は初回です。

目次

  1. 日本語入力時の画面高さの調整
  2. ステータスバー、あるいは SafeArea(ノッチ部)の色
  3. 【次回以降予定】アプリ情報の Web 側への引き渡し
  4. 【次回以降予定】<input type="xxx"> への対応
  5. 【次回以降予定】Back ボタンハンドリングの Web 側への移譲
  6. 【次回以降予定】スプラッシュスクリーンおよび初回読み込み時の対応

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.xmlcolorPrimaryDark の色を修正します。下の例では #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

image.png

これで、ステータスバーの文字色が白になります。

対応結果

対応後の見た目はこんな感じになります。結構ネイティブアプリっぽくなって来たでしょう?

対応後(Android) 対応後(iOS)

まとめ

とりあえず初回ということで、見た目中心の対応内容を挙げてみました。
一通り揃ったら、GitHub にサンプルを上げて、あわよくば可能なところは nuget パッケージに切り出して利用できるようにしていきたいと思います。

見た目の対応でも、ダークテーマ対応などはまったく無知なのでこれから勉強していきます。
次回エントリは、、、年内いける・・・かな?

参考(次回以降のも含む)

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

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キーボードを使ったが快適に使える。

Screenshot_20191214-235114.png

aptで環境構築する

aptでgfortranをインストールする。ちゃんとapt updateしないとインストールでNot Foundエラーが出る。

apt update && apt install gfortran emacs

プログラミング

あとは通常のCUIプログラミングと一緒

Screenshot_20191215-000955.png

Screenshot_20191215-000944.png

無事hello worldが実行できました。

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

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-unityCloud Storage for Firebase Quickstartを活用します。

一見、FirebaseもUnityも古いバージョンで作られていますが、大幅な変更がない最低限の機能はちゃんと動くので、本格的なアプリへの導入の際の設計・開発で参考にしていきましょう。

Unity側の調整

ですが、活用すると言ってもquickstart-unity/storage/testapp/Assets/Firebase/Sample/Storage/UIHandler.cs
だけ扱うので、クラス名だけ変えてC#スクリプトを作成し、コピペしましょう。

その後、コピペして作ったC#を空のGameObjectにアタッチし、そのInspector上に表示されるGUISkin変数にGUISkinを作成してアタッチしましょう。

スクリーンショット 2019-12-14 23.03.40.png

また、カメラの調整をSkyboxからSolid Colorに変更して、サンプルが分かりやすいように調整しておきましょう。

スクリーンショット 2019-12-14 23.14.04.png

Cloud Storageの設定

次にFirebaseコンソール側の作業になります。

Storageのメニューを開き、事前に画像などをアップし、詳細上から画像のリンクをコピーなどしてメモしておきましょう。

次にルールの設定です。
公式の『Storage セキュリティ ルールを使ってみる』に各ルールの設定サンプルがあり、Authを扱わないので今回は公開のルールを扱います。
この公開のルールは、誰でも読み込みと書き込みが可能な設定なので作業終了後に設定を戻しておくようにしておきましょう(自己責任でお願いします?)。

スクリーンショット 2019-12-14 23.04.33.png
スクリーンショット 2019-12-14 23.08.35.png
スクリーンショット 2019-12-14 23.08.52.png

実行

Local File PathStorage Locationの設定を先ほどメモした内容に書き換えてDownload Bytes/Download Stream/Download to Fileの各ボタンを押してみると以下のようになります。

成功せず、もしパーミッションエラーで403がある場合は、おそらくルールの設定変更忘れだと思います。
また、Local File Pathの変更を忘れているとデフォルトで設定されているdownloaded_file.txtの名前でファイルが生成されてしまいます。

スクリーンショット 2019-12-14 23.15.11.png
スクリーンショット 2019-12-14 23.15.18.png
スクリーンショット 2019-12-14 23.15.51.png

さいごに

あとはAssetBundleさえ準備できればいつでもFirebaseで管理できるようになりそうですね。

ちなみにCloud Storageのファイルサイズには上りと下りで制限があるのか気になりましたでしょうか。
Storage セキュリティ ルールを使ってみる』を読んでいるとルール側でデータサイズを指定して上り下りの制御ができるようです。

ちょっと調べてみたところデータサイズの制限は特にないようなので、普通にAssetBundleのサーバとして扱えそうですよね^^

 

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