- 投稿日:2020-11-12T23:34:19+09:00
【Lv1】イベントとリスナ【Android/Kotlin】
環境
macOS Catalina version10.15.7
Android Studio 4.1.1
kotlin 1.4.10
実機: Pixel 4a (5G)コード
参考文献:
・基礎&応用力をしっかり育成!Androidアプリ開発の教科書 Kotlin対応 なんちゃって開発者にならないための実践ハンズオン
WINGSプロジェクト 齊藤 新三著 山田 祥寛 (監修)イベントとリスナって?
用語 意味 イベント ユーザー画面から受け取る何らかの操作のこと イベントハンドラ イベントを受け取って行う処理のこと リスナ イベントが発生するかどうか見張ってるもの プロジェクトを作って自動生成されたコードを見てみると、onCreateメソッドがすでにありますね。
onCreateメソッドは、アプリが起動するとまず実行されるメソッドです。今回作成するクリックイベントリスナもここに
MainActivity.ktactivity_main override fun onCreate(savedInstanceState: Bundle?) //oncreateメソッドはアプリが起動するとまず実行されるonCreateメソッドの外にクリックイベントに対する専用リスナを設定していきます。
MainActivity.ktprivate inner class HelloListener : View.OnClickListener { //クリックイベントに対する専用リスナー //リスナーは表示ボタンにイベントが発生するのを見守っている override fun onClick(v: View?) { TODO("Not yet implemented") } }それでは、oneCreateメソッド内にイベントハンドラを書いていきましょう。
MainActivity.ktprivate inner class HelloListener : View.OnClickListener { //クリックイベントに対する専用リスナー override fun onClick(v: View?) { //findViewByIdでこのIdを持つEditTextオブジェクトを取得 val input = findViewById<EditText>(R.id.etName) //findViewByIdでこのIdを持つTextViewオブジェクトを取得 val output = findViewById<TextView>(R.id.tvOutput) //入力受付 val inputStr = input.text.toString() //出力 output.text = inputStr + "さん、こんにちは!" } }HelloListenerクラスは、クリックイベントに対する専用リスナーとして働きます。
イベントハンドラの処理は、
オブジェクトを取得->入力された文字列を取得->出力
といった流れです。それでは、onCreateメソッド内にリスナを設定します。
MainActivity.ktoverride fun onCreate(savedInstanceState: Bundle?) { //oncreateメソッドはアプリが起動するとまず実行される super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) //リスナの設定 val btClick = findViewById<Button>(R.id.btClick) //表示ボタンのオブジェクトを取得 //消去イベント val btClear = findViewById<Button>(R.id.bt_clear) //消去ボタンのオブジェクトを取得 val listener = HelloListener() //リスナクラスのインスタンスを作成 btClick.setOnClickListener(listener) //表示ボタンにクリックイベントのリスナを設定 }2個目のボタンイベントを追加
1個目同様に、onCreateメソッドにインスタンスを作成してリスナを設定します。
MainActivity.ktoverride fun onCreate(savedInstanceState: Bundle?) { //oncreateメソッドはアプリが起動するとまず実行される super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) //リスナの設定 val btClick = findViewById<Button>(R.id.btClick) //表示ボタンのオブジェクトを取得 //消去イベント val btClear = findViewById<Button>(R.id.bt_clear) //消去ボタンのオブジェクトを取得 val listener = HelloListener() //リスナクラスのインスタンスを作成 btClick.setOnClickListener(listener) //表示ボタンにクリックイベントのリスナを設定 btClear.setOnClickListener(listener) //消去ボタンイベントのリスナを設定 }2個目のボタンに対するクリックイベントもHelloListenerクラスを使うもですが、whenを使ってボタンにつけたidによって処理を分けるようにしてみましょう。
HelloListennerクラスの中に記述します。
MainActivity.ktif (v != null) { when(v.id){ //表示ボタンの場合 R.id.btClick->{ val inputStr = input.text.toString() output.text = inputStr + "さん、こんにちは!!" } //消去ボタンの場合 R.id.bt_clear ->{ //入力欄を空 input.setText("") //メッセージ表示欄を空文字に設定 output.text = "" } } }
- 投稿日:2020-11-12T18:22:12+09:00
無料VPNアプリのすゝめ
1.はじめに
今回は端的に無料VPNアプリについて書きたいと思います。
VPNには様々な以下のような用途があります。
・国を偽装したいとき
別に悪い意味ではなく、国によって見れるコンテンツが制限されているのを見れるようにするための偽装
・ネットワーク監視、制限から逃れたいとき
家庭、職場によって、ネットワーク監視、制限がついていたり、中国やロシアなどは国自体でネットワーク監視、制限がついているため、それから逃れるため。
・フリーWiFiなど安全が保証されないところでのプライバシー保護
これは二番目にあげたこととほぼ同じですが、フリーWiFiは第三者に監視される可能性があるため。これ以外にも用途があるとは思いますが、メジャーなパッと思いつく用途はこんな感じです。
PCでは筑波大学がやっている無料VPNがあるため、それが一番に来ると思うのですが、スマホでは色々なVPNがあるため、どれが一番いいVPNアプリかを知りたかったので、今回は15種類ほどのアプリを速度、金額の観点から検証したので、そこで一番良かったアプリを紹介します。2.Proxy Master
僕が一番良いと思うアプリはProxy Masterです。
名前が似ているものが多数あるため注意が必要です。(上のリンクを踏めば問題ないと思います。)
なぜこれをおすすめする理由は以下のとおりです。
・通信速度が速い
VPNをつないだときとつないでいないときで、あまり通信速度の差を感じなかったです。VPNでパフォーマンスがおちるというのは、かなり抵抗があったのでこれはとても嬉しいかなと思います。
・無料
VPNにお金をかけたくはないけど、手軽にVPNをつかいたいと思っていたのでこれも嬉しい機能
・広告があまり鬱陶しくない
無料と言っても30秒級の広告が当然のように出てくるアプリもありました。さすがに使う前に30秒と通信費をかけていたら、元も子もありません。しかしこのアプリの広告は5秒の広告しかなく、動画ではなく画像の広告なので、あまり時間も取られず、外でも安心して使うことができます。以上の理由から、このアプリを推したいと思います。
3.最後に
どうだったでしょうか。VPNを使いたかったけど、もしくは一回使ってみたけど、「無料じゃないからいいや」とか「広告がたくさんあるしいいや」という理由で敬遠していた人にとってはいい記事になったと思います。もしこの記事がよかったらLGTMよろしくおねがいします。
- 投稿日:2020-11-12T15:52:03+09:00
[Android][NDK]ndk-buildについて
業務で調べたことを自分自身の理解を含めるために文書にする。
AndroidのNDKでビルドする主な方法は以下の3つ。
- ndk-build
- CMake
- スタンドアロン・ツールチェーン
今主流なのはCMakeだが、取り組んでいる業務で使用するのがndk-buildなため、ndk-buildについてまとめる。
ndk-buildスクリプトは、GNU Makeを利用したビルドシステムだ。ndk-buildスクリプトは、以下のコマンドを実行することと同等。
$ make -f /path/to/NDK/build/core/build-local.mkndk-buildスクリプトは、以下のように呼び出す。
$ cd /path/to/project $ /path/to/NDK/ndk-buildAndroid.mk
ソースとライブラリの情報を記述するもので、jni/ディレクトリ配下(サブディレクトリ配下でも問題ない)に置かれる。
以下は、Android.mkの例。
# Android.mkが置かれているパスを設定。 LOCAL_PATH := $(call my-dir) # LOCAL_PATH 以外のLOCAL_XXX変数をクリアする。 include $(CLEAR_VARS) # モジュール名を設定(生成されるライブラリ名は libhello-jni.so となる)。 LOCAL_MODULE := hello-jni # 生成するライブラリ名を libhello.so に設定する。 LOCAL_MODULE_FILENAME := libhello # ソースファイルを設定 LOCAL_SRC_FILES := hello-ini.c utils.c # 共有ライブラリを生成する。 include $(BUILD_SHARED_LIBRARY)共有ライブラリ生成の記述を以下に差し替えると静的ライブラリが生成される。
include $(BUILD_STATIC_LIBRARY)ターゲットとなるCPUとアーキテクチャは、Application.mkのAPP_ABI変数で指定され、ABIごとにAndroid.mkが評価される。
APP_ABI := armeabi-v7a arm64-v8a x86TARGET_ARCH変数に、CPUファミリー(arm, arm64, x86, x86_64)が設定される。
TARGET_PLATFORM変数に、Android API レベル番号(例えば、"android-22")が設定される。
TARGET_ARCH_ABI変数に、CPUとアーキテクチャ(armeabi-v7a, arm64-v8a, x86, x86_64)が設定される。
TARGET_ABI変数に、Android API レベルとABI(例えば、"android-22-arm64-v8a")が設定される。
インクルードファイルの検索パスの設定は以下の通り。
LOCAL_C_INCLUDES := sources/foo複数の値が設定できる変数では、以下のように値が追加できる。
LOCAL_C_INCLUDES += $(LOCAL_PATH)/ディレクトリ名/foo依存している静的ライブラリの設定。
現在のモジュールが共有ライブラリなら、この共有ライブラリを利用する先に自動で変数に設定された静的ライブラリがリンクされる。
現在のモジュールが静的ライブラリならリンクされない。LOCAL_STATIC_LIBRARIES += hello-jnicocos2d-xのように、C/C++コードをlibgame.soという共有ライブラリに固め、Java/Kotlinコードのビルドで繋げる場合、この共有ライブラリが依存している静的ライブラリを取り込むため、以下の変数に静的ライブラリを設定する。
LOCAL_WHOLE_STATIC_LIBRARIES += hello-jni現在のモジュールを利用先に変数の値を渡す場合は、LOCAL_EXPORT_XXX変数を利用する。
LOCAL_EXPORT_CFLAGS LOCAL_EXPORT_CPPFLAGS LOCAL_EXPORT_C_INCLUDES LOCAL_EXPORT_LDFLAGS LOCAL_EXPORT_LDLIBSAndroid.mkの例のmy-dirのように、NDKはGNU Make関数マクロが用意している。
関数マクロは、他のAndroid.mk呼び出しの影響を受けるため、Android.mkの末尾に記述する。以下の例では、my-dirが返す値が、1個目と2個目で異なっている。
LOCAL_PATH := $(call my-dir) include $(LOCAL_PATH)/foo/Android.mk LOCAL_PATH := $(call my-dir)import-moduleマクロ関数を利用すると、任意のモジュールのAndroid.mkをモジュール名で指定できる。
$(call import-module,モジュール名)上記の例では、NDK_MODULE_PATH環境変数が参照するディレクトリで設定した名前でモジュールを検索して、そのモジュールのAndroid.mkがインクルードされる。
ビルド済みライブラリを提供する場合のAndroid.mkの例。
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := foo-prebuilt LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/../include LOCAL_SRC_FILES := ../libs/libfoo.so include $(PREBUILT_SHARED_LIBRARY)上記のようにヘッダーファイルのパスをLOCAL_EXPORT_C_INCLUDESに設定しておくと、利用する側は楽になる。
静的ライブラリの場合は、PREBUILT_SHARED_LIBRARYがPREBUILT_STATIC_LIBRARYになる。
include $(PREBUILT_STATIC_LIBRARY)ビルド済みライブラリを利用するAndroid.mkの例。
include $(CLEAR_VARS) LOCAL_MODULE := foo-user LOCAL_SRC_FILES := foo-user.c LOCAL_SHARED_LIBRARIES := foo-prebuilt include $(BUILD_SHARED_LIBRARY)ビルド済みライブラリが静的ライブラリの場合は、LOCAL_SHARED_LIBRARIESがLOCAL_STATIC_LIBRARIESになる。
【関連情報】
ndk-build
Cocoa.swift
Cocoa勉強会 関東
Cocoa練習帳
- 投稿日:2020-11-12T11:18:10+09:00
Android カスタムView
Android カスタムViewを作成する際は、下記コンストラクタを記述すること
AttributeSet attrsが必要MyView.java public class MyView extends View { public MyView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onDraw(Canvas canvas) { } }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"> <com.test.android.viewtest.MyView android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
- 投稿日:2020-11-12T10:21:14+09:00
2020年になってNexus 6Pに古いAndroidバージョンのFactory Imageを焼こうとしたら妙に苦戦したのでメモを残す
経緯
- Unity 2018.4.27f1+特定SoC+Android 8.0未満でsubmeshのBlendShapeが正常に動かない問題が出た
- 動かないならまだいいが、おかしな値が入って画が崩れる(泣)
- SoCは今のところ Snapdragon 430, 617, 810が該当してそう
- Unity 2018.2.20f1では出てなかった
- 原因はわかっていない
- 本件はバグに関連して、Snapdragon 810搭載のNexus 6PをAndroid 8.0未満にすべく、古いイメージを焼こうとした
- 妙に苦戦したのでメモを残しておく
手順
1. Factory Imageのzipをダウンロードする
Factory Images for Nexus and Pixel Devices
https://developers.google.com/android/images2. 手順通りだとエラーになる
archive does not contain 'boot.sig' fastboot: error: Couldn't parse partition size '0x'.
- boot.sigがないのはzipを展開して直接fastboot flashすると回避できる模様
- 参考:https://qiita.com/ytabuchi/items/28d66b7e69146086fe13
- partitionが見つからないのは
flash:rawという生焼き(?)コマンドで回避できる模様- 参考:https://forum.xda-developers.com/nexus-6p/help/getting-fastboot-error-parse-partition-t4156777
3. flash-all.shを以下のように書き換えて実行する
- 中に入ってるzipもフラットに展開する
- vendor.imgがあるのでそっちを焼くコマンドも足す
- boot.imgはflashではなくてflash:rawにする
- bootloaderやradioのバージョンはダウンロードしたFactory Imageによって違うので適宜差し替えて読むこと
fastboot flash bootloader bootloader-angler-angler-03.62.img fastboot reboot-bootloader sleep 5 fastboot flash radio radio-angler-angler-03.78.img fastboot reboot-bootloader sleep 5 # fastboot -w update image-angler-nmf26f.zip fastboot flash vendor vendor.img fastboot reboot-bootloader sleep 5 fastboot flash recovery recovery.img fastboot flash:raw boot boot.img fastboot flash system system.img fastboot flash cache cache.img fastboot flash userdata userdata.img fastboot reboot
- 投稿日:2020-11-12T09:40:00+09:00
【Android】Navigation Safe Args でリソースファイルに指定された値をデフォルト値として渡したい
最近、Android Jetpack のNavigationを使っているのですが、Fragment 間の画面遷移が簡単に実現できて便利だなと感じています。
ですが、ハマりポイントもありましたので備忘録として残しておきます。Safe Args でリソースファイルに指定された値をデフォルト値として渡したい
Safe Argsとは、ナビゲーションやデータの受け渡しをする際、型の安全性を保証してくれる仕組みです。
strings.xmlに定義した文字列をデフォルト値として渡したいとします。strings.xml<string name="message">テストメッセージ</string>フラグメントなどのレイアウト、ナビゲーション グラフをそれぞれ下記のように実装します。
各コードの説明はここではしません。
Navigation の説明については下記の記事などがわかりやすかったです。
- [Android] 10 分で作る、Navigation による画面遷移 - Qiita
- [Android] Navigation で SafeArgs を使って引数付き画面遷移をする - Qiita
FirstFragment.ktclass FirstFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val view = inflater.inflate(R.layout.fragment_first, container, false) view.button.setOnClickListener { val action = FirstFragmentDirections.actionFirstFragmentToSecondFragment() it.findNavController().navigate(action) } return view } }fragment_first.xml<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".view.FirstFragment"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/button" android:id="@+id/button"/> </FrameLayout>SecondFragment.ktclass SecondFragment : Fragment() { private val args: SecondFragmentArgs by navArgs() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val view = inflater.inflate(R.layout.fragment_second, container, false) view.text_view.text = args.message return view } }fragment_second.xml<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".view.SecondFragment"> <TextView android:id="@+id/text_view" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout>nav_graph.xml<?xml version="1.0" encoding="utf-8"?> <navigation 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:id="@+id/nav_graph" app:startDestination="@id/firstFragment"> <fragment android:id="@+id/firstFragment" android:name="com.example.view.FirstFragment" android:label="fragment_first" tools:layout="@layout/fragment_first"> <action android:id="@+id/action_firstFragment_to_secondFragment" app:destination="@id/secondFragment" /> </fragment> <fragment android:id="@+id/secondFragment" android:name="com.example.view.SecondFragment" android:label="fragment_second" tools:layout="@layout/fragment_second"> <argument android:name="message" android:defaultValue="@string/message" app:argType="string" /> </fragment> </navigation>activity_main.xml<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <androidx.fragment.app.FragmentContainerView android:id="@+id/nav_host_fragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:defaultNavHost="true" app:navGraph="@navigation/nav_graph" /> </androidx.constraintlayout.widget.ConstraintLayout>ですが、アプリ起動時に以下のようなログを出力してクラッシュします。
2020-11-11 22:14:44.768 9509-9509/com.example E/AndroidRuntime: FATAL EXCEPTION: main Process: com.example, PID: 9509 java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example/com.example.MainActivity}: android.view.InflateException: Binary XML file line #14 in com.example:layout/activity_main: Binary XML file line #14 in com.example:layout/activity_main: Error inflating class androidx.fragment.app.FragmentContainerView at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3270) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409) at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016) at android.os.Handler.dispatchMessage(Handler.java:107) at android.os.Looper.loop(Looper.java:214) at android.app.ActivityThread.main(ActivityThread.java:7356) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930) Caused by: android.view.InflateException: Binary XML file line #14 in com.example:layout/activity_main: Binary XML file line #14 in com.example:layout/activity_main: Error inflating class androidx.fragment.app.FragmentContainerView Caused by: android.view.InflateException: Binary XML file line #14 in com.example:layout/activity_main: Error inflating class androidx.fragment.app.FragmentContainerView Caused by: java.lang.RuntimeException: Exception inflating com.example:navigation/nav_graph line 22 at androidx.navigation.NavInflater.inflate(NavInflater.java:90) at androidx.navigation.NavController.setGraph(NavController.java:499) at androidx.navigation.NavController.setGraph(NavController.java:481) at androidx.navigation.fragment.NavHostFragment.onCreate(NavHostFragment.java:237) at androidx.fragment.app.Fragment.performCreate(Fragment.java:2685) at androidx.fragment.app.FragmentStateManager.create(FragmentStateManager.java:280) at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1187) at androidx.fragment.app.FragmentManager.addAddedFragments(FragmentManager.java:2236) at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2009) at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1965) at androidx.fragment.app.FragmentManager.execSingleAction(FragmentManager.java:1830) at androidx.fragment.app.BackStackRecord.commitNowAllowingStateLoss(BackStackRecord.java:303) at androidx.fragment.app.FragmentContainerView.<init>(FragmentContainerView.java:166) at androidx.fragment.app.FragmentLayoutInflaterFactory.onCreateView(FragmentLayoutInflaterFactory.java:51) at androidx.fragment.app.FragmentController.onCreateView(FragmentController.java:135) at androidx.fragment.app.FragmentActivity.dispatchFragmentsOnCreateView(FragmentActivity.java:356) at androidx.fragment.app.FragmentActivity.onCreateView(FragmentActivity.java:335) at android.view.LayoutInflater.tryCreateView(LayoutInflater.java:1069) at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:997) at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:961) at android.view.LayoutInflater.rInflate(LayoutInflater.java:1123) at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:1084) at android.view.LayoutInflater.inflate(LayoutInflater.java:682) at android.view.LayoutInflater.inflate(LayoutInflater.java:534) at android.view.LayoutInflater.inflate(LayoutInflater.java:481) at androidx.appcompat.app.AppCompatDelegateImpl.setContentView(AppCompatDelegateImpl.java:696) 2020-11-11 22:14:44.771 9509-9509/com.example E/AndroidRuntime: at androidx.appcompat.app.AppCompatActivity.setContentView(AppCompatActivity.java:170) at com.example.MainActivity.onCreate(MainActivity.kt:13) at android.app.Activity.performCreate(Activity.java:7802) at android.app.Activity.performCreate(Activity.java:7791) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1299) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3245) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3409) at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016) at android.os.Handler.dispatchMessage(Handler.java:107) at android.os.Looper.loop(Looper.java:214) at android.app.ActivityThread.main(ActivityThread.java:7356) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930) Caused by: org.xmlpull.v1.XmlPullParserException: unsupported value 'テストメッセージ' for string. You must use a "reference" type to reference other resources. at androidx.navigation.NavInflater.inflateArgument(NavInflater.java:206) at androidx.navigation.NavInflater.inflateArgumentForDestination(NavInflater.java:146) at androidx.navigation.NavInflater.inflate(NavInflater.java:121) at androidx.navigation.NavInflater.inflate(NavInflater.java:132) at androidx.navigation.NavInflater.inflate(NavInflater.java:81) ... 42 more原因
app:argType="string"に対して、android:defaultValue="@string/message"となっており、データ型が一致していないのが原因です。Caused by: org.xmlpull.v1.XmlPullParserException: unsupported value 'テストメッセージ' for string. You must use a "reference" type to reference other resources.一見すると良さそうに見えるのですが、
@string/messageは String そのものではなくリソース ID(int)なので、データ型が異なると見なされるようです。解決策
nav_graph.xmlのapp:argTypeをreferenceに変更します。nav_graph.xml... <argument android:name="message" android:defaultValue="@string/message" app:argType="reference" /> ...そうするとリソース ID を渡すよう変更されるので、
SecondFragmentを下記のように変更します。SecondFragment.kt... view.text_view.text = getString(args.message) ...これで起動時にクラッシュしなくなり、正常に実行できるようになります。
- 投稿日:2020-11-12T01:19:40+09:00
Jetpack Compose 湯婆婆!!
湯婆婆ネタが流行っているなーと眺めていたら、Jetpack Compose 版がないじゃないかと思ったので、作ってみました。
(Jetpack Compose 湯婆婆!!って飛んでいきそうな勢いがあって好きです。作中でも時々飛んでいましたっけ?)元ネタ:Javaで湯婆婆を実装してみる
そもそも Jetpack Composeって?
Jetpack Compose は、Android のネイティブ UI を構築するための最新のツールキットです。
細かい説明はこちらをどうぞ
開発環境
- Jetpack Compose は
Android Studio 4.2 Preview版で開発できます。- まだアルファ版ですので、本格的な開発には向かないですが、今回のようなネタ開発にはちょうど良いお題
![]()
コード
処理は
MainActivityとYubabaViewModelに分かれています。MainActivity.ktclass MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { YubabaAppTheme { Surface(color = MaterialTheme.colors.background) { YubabaTalk() } } } } } @Composable fun YubabaTalk(yubabaViewModel: YubabaViewModel = viewModel()){ val isSend: Boolean by yubabaViewModel.sendState.observeAsState(initial = false) val inputName: String by yubabaViewModel.inputName.observeAsState("") val yubabaNaming: String by yubabaViewModel.yubabaNaming.observeAsState(initial = "") Column( modifier = Modifier.padding(16.dp), ) { Text( text = "契約書だよ。そこに名前を書きな。", style = typography.h5, modifier = Modifier.padding(bottom = 8.dp) ) val textState = remember { mutableStateOf(TextFieldValue()) } TextField( value = textState.value, onValueChange = { if (!isSend) { textState.value = it } }, backgroundColor = if (isSend) Color.Transparent else Color.DarkGray, modifier = Modifier.padding(bottom = 8.dp) ) when { !isSend -> { Button( onClick = { yubabaViewModel.onSendStateChanged(true) yubabaViewModel.onNameSend(textState.value.text) }, modifier = Modifier.padding(bottom = 8.dp) ) { Text("名前を教える") } } isSend -> { Text( text = "フン。${inputName}というのかい。贅沢な名だねぇ。", style = typography.h5, modifier = Modifier.padding(bottom = 8.dp) ) Text( text = "今からお前の名前は${yubabaNaming}だ。いいかい、${yubabaNaming}だよ。分かったら返事をするんだ、${yubabaNaming}!!", style = typography.h4 ) } } } }YubabaViewModel.ktclass YubabaViewModel: ViewModel() { private val _sendState = MutableLiveData(false) val sendState: LiveData<Boolean> = _sendState private val _inputName = MutableLiveData("") val inputName: LiveData<String> = _inputName private val _yubabaNaming = MutableLiveData("") val yubabaNaming: LiveData<String> = _yubabaNaming fun onSendStateChanged(sendState: Boolean) { _sendState.value = sendState } fun onNameSend(name: String) { _inputName.value = name val newNameIndex: Int = Random.nextInt(name.length) val editName = name.substring(newNameIndex, newNameIndex + 1) _yubabaNaming.value = editName } }解説
MainActivity
- Android ですので
Activityのライフサイクルに合わせた実装になります。setContentブロックの中で、表示するUIの構築を実施します。
- UIの構築はコンポーズ可能な関数を用います。
@Composableアノテーションをメソッドに付与する事で、その中のUIが構築される動きになります。- UIの書き方は Android 特有の XMLではなく、
Text、TextField、ButtonなどComposeUIを呼び出して登録していきます。Columnはそのまま列を構築するComposeUIです。(ListViewやGridView的なイメージのもの)- ComposeUIの多くは状態を持たない特性があるので、今の状態なんかはViewModelやプロパティーで保持しています。
textStateは常にテキストの入力値を持っています。isSendは名前を教えたかの状態inputNameは入力した名前yubabaNamingは湯婆婆に決めてもらった名前- LiveDataの状態を ComposeUI へ反映させるため、
observeAsState(初期値)を利用しています。下のライブラリを取り込んでおいてください。build.gradledependencies { implementation "androidx.compose.runtime:runtime-livedata:$compose_version" }YubabaViewModel
- Android の
androidx.lifecycle.ViewModel()を元にしたViewModelで、保持している値はLiveDataを利用して伝えています。うごきはこんな感じ
- 0文字入力時には、しっかり落ちます
![]()
感想
- 実はちゃんと Jetpack Compose にさわってアプリっぽく作ったのは初めてな気がします。
- 状態を持たないという特性を理解するのにちょっと苦労しました!
- まだまだ Jetpack Compose はα版ですので、いろいろと変わるかなーとおもっていますが、なかなかおもしろかったです
![]()
追記(2020/11/12)
- Jetpack Compose のViewModelからの購読処理には
MutableStateもCompose用に用意されていますので、MutableState版も差分箇所だけ載せておきます。MainActivity.kt@Composable fun YubabaTalk(yubabaViewModel: YubabaViewModel = viewModel()){ val isSend: Boolean = yubabaViewModel.sendState val inputName: String = yubabaViewModel.inputName val yubabaNaming: String = yubabaViewModel.yubabaNaming //・・・省略 }YubabaViewModel.ktclass YubabaViewModel: ViewModel() { var sendState: Boolean by mutableStateOf(false) private set var inputName: String by mutableStateOf("") private set var yubabaNaming: String by mutableStateOf("") private set fun onSendStateChanged(sendState: Boolean) { this.sendState = sendState } fun onNameSend(name: String) { this.inputName = name val newNameIndex: Int = Random.nextInt(name.length) val editName = name.substring(newNameIndex, newNameIndex + 1) yubabaNaming = editName } }

