- 投稿日:2021-01-08T21:48:07+09:00
Flutter (Android) で、 任意のpluginをApplication.ktに読み込む
やりたいこと
Application.ktimport io.flutter.app.FlutterApplication import io.flutter.plugin.common.PluginRegistry import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback import io.flutter.plugins.GeneratedPluginRegistrant import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService ...みたいな、Application.kt のimportの部分を、任意のプラグインに対して定義したい
解決策
Application.ktimport io.flutter.plugins.firebase.firestore.FlutterFirebaseFirestorePlugin該当するパッケージのgithub repoをみる。
cloud firestoreの場合、ここ
cloud_firestore/android/src/main/java
以下の、io/flutter/plugins/firebase/firestore
のところがそれになる。
- 投稿日:2021-01-08T15:43:46+09:00
AndroidアプリにHMSのAnalytics KitとCrashlyticsを3ステップで導入する手順
HMSのAnalytics KitとCrashlyticsについて
Androidアプリを分析したり、クラッシュの原因を解析したりするのに、グーグルのFirebaseがよく使われています。
ところが、実はファーウェイもFirebaseと同等なサービスを提供しています。そのサービスはAnalytics KitとAppGallery Connect Crashlyticsです。HMS Analytics KitとAppGallery Connect Crashlyticsに数多くの機能が含まれているので、具体的な使い方はオフィシャルサイトをご参照いただければ幸いです。本稿はすぐ導入できる実装方法のみを紹介させていただきます。
3ステップで導入する手順
ステップ1
プロジェクトのbuild.gradleにHMSのリポジトリを追加します。
build.gradlebuildscript { repositories { ... // HMSのリポジトリを追加 maven {url 'http://developer.huawei.com/repo/'} } dependencies { ... // AppGallery Connectのライブラリを追加 classpath 'com.huawei.agconnect:agcp:1.3.1.300' } } allprojects { repositories { ... // HMSのリポジトリを追加 maven { url 'http://developer.huawei.com/repo/' } } }モジュールのbuild.gradleにHMS Analytics KitのライブラリとAppGallery Connect Crashlyticsのライブラリを追加します。
build.gradleapply plugin: 'com.android.application' ... // こちらの行をapply plugin: 'com.android.application'の後ろに追加 apply plugin: 'com.huawei.agconnect' ... dependencies { ... // AppGallery Connectのライブラリを追加 implementation 'com.huawei.agconnect:agconnect-core:1.3.1.300' // HMS Analytics Kitのライブラリを追加 implementation 'com.huawei.hms:hianalytics:5.0.3.300' // AppGallery Connect Crashlyticsのライブラリを追加 implementation 'com.huawei.agconnect:agconnect-crash:1.4.1.300' }ステップ2
AndroidManifest.xmlに次のようにmeta-dataを追加します。
AndroidManifest.xml<?xml version="1.0" encoding="utf-8"?> <manifest ... > <application ... > <meta-data android:name="install_channel" android:value="AppGallery"> </meta-data> ... </application> </manifest>ステップ3
HiAnalyticsを初期化します。
MainActivity.ktoverride fun onCreate(savedInstanceState: Bundle?) { ... HiAnalyticsTools.enableLog() val hiAnalytics = HiAnalytics.getInstance(this) }これで実装が完了です。
最後
HMS Analytics KitとAppGallery Connect Crashlyticsは非常に簡単に導入できるので、HMSアプリを開発するときに、HMS Analytics KitとAppGallery Connect Crashlyticsも合わせて導入することをお勧めします。
参考
- HMS:https://developer.huawei.com/consumer/jp/
- HMS Analytics Kit:https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides-V5/introduction-0000001050745149-V5
- AppGallery Connect Crashlytics:https://developer.huawei.com/consumer/en/doc/development/AppGallery-connect-Guides/agc-crash-introduction-0000001055732708
- Huawei Developers:https://forums.developer.huawei.com/forumPortal/en/home
- Facebook Huawei Developersグループ:https://www.facebook.com/Huaweidevs/
- 投稿日:2021-01-08T15:37:48+09:00
BindingAdapterのすゝめ
検証環境
この記事の内容は、以下の環境で検証しています。
* Java:open jdk 1.8.0_152
* Kotlin 1.3.61
* Android Studio 3.5.3
* CompileSdkVersion:29はじめに
Jatpackに含まれているData Binding(以降、データバインディングと表記)を利用するとグルーコードは減少します。
更に、双方向バインディングを行うことにより、リアクティブなプログラミングが可能になります。しかし、データバインディングを深く理解するとより便利な記述ができるようになります。
この記事では、データバインディングの応用的な話であるBindingAdapterについて記述します。データバインディングの基本動作
そもそも、データバインディングがどの様に動作しているか理解していなかったので、調べてみました。
公式サイトを今一度読み直してみると驚きの動きをしていました。データバインディングはレイアウトの属性から適切なsetterメソッドを探し出します。
メソッド名と引数を手がかりに一致するメソッドを呼び出していたんです。
絵にしてみるとこんな感じでしょうか?実はこのデータバインディングに対してカスタマイズができます。
ここからは、オリジナルの属性を作って、データバインディングで渡された値を使って、独自の処理を追加する話になります。BindinAdapterを使ってみよう
オリジナルの属性の定義と独自の処理の実装を行う上で、大切になるのが @BindingAdapter アノテーションです。
題材として以下を扱っていきます。
- データバインディングでDate型を渡された時にフォーマットして表示する
- データバインディングでURLを渡された時に、画像をサーバから取得し表示する。もし、取得に失敗した場合、代替の画像を表示する
最終的に下図のような動きをさせます。
メリット
ここで思うこともあります。
わざわざオリジナルの属性を追加する意味はあるのでしょうか?
ActivityとかViewModelでやればいいじゃない!!と考えられなくもありません。
しかし、この様な汎用的な処理はどこに定義するか迷います。
良きところに定義出来たとしても、必要に応じて毎回処理を呼び出し、グルーコードを記述しなければなりません。しかし、BindingAdapterを利用することにより、処理の呼び出しをフレームワークが適宜選択してくれます。
その分、グルーコードも減ります。
結果として、下図のようなメリットを受けられるわけです。オリジナルの属性を作ろう
早速BindingAdapterを利用してみましょう。
build.gradle
おそらく、多くの方はデータバインディングするので、例のアレを追記すればいいんでしょ?って思っていると思います。
そうです。まずは、データバインディングを有効にしていきます。build.gradleandroid { ・・・省略・・・ dataBinding { enabled = true } }ここで油断してはいけないことがあります。今回は@BindingAdapter アノテーションを利用します。
ということは、 kotlin-kapt プラグインも必要になります。下記も追記しないと私のようにハマりますよ。
build.gradleapply plugin: 'kotlin-kapt'最後に、今回の例で画像はPicassoを使っているのでdependenciesに下記を追記しました。
build.gradleimplementation 'com.squareup.picasso:picasso:2.71828'これで事前の準備が終わりました。
日付のフォーマット
ここから、 @BindingAdapter を付与したメソッドを定義します。
まずは、オブジェクトを宣言します。
オブジェクト名は任意です。object BindingAdapters { }続いて、その中にメソッドを宣言します。
初めに、Date型からフォーマットして表示する処理のメソッドです。@BindingAdapter("convertDate") @JvmStatic fun convertDate(view:TextView, date:Date){ view.text = SimpleDateFormat("yyyy/MM/dd hh:mm:ss").format(date) }@BindingAdapter("convertDate") を見てみるとvalueとして convertDate を渡しています。
convertDate がオリジナルの属性となります。
実際のレイアウトファイルのコードでは以下のようになります。<TextView android:id="@+id/textView" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginLeft="16dp" android:layout_marginEnd="16dp" android:layout_marginRight="16dp" android:layout_marginBottom="16dp" app:convertDate="@{now}" ←ここが重要 android:textAppearance="@style/TextAppearance.AppCompat.Large" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" />@BindingAdapterの直下に @JvmStatic アノテーションを付与しています。
@JvmStatic が無いと動作しないため、必ず付与してください。続いて、引数を見てみましょう。
view:TextView, date:Date となっています。
第一引数には、属性を付与したViewのオブジェクトが取得できます。今回はTextViewにしか付与しないのでTextView型で受け取っています。
第二引数には、属性に設定した値が格納されてきます。
今回だと、「@{url}」となっているので、urlに格納されている値がそのまま格納されます。最後にメソッドの内容を確認します。
view.text = SimpleDateFormat("yyyy/MM/dd hh :mm :ss").format(date)
単純にDate型をフォーマットして、Textに設定しています。メソッドのどの部分がレイアウトファイルのどの部分に影響しているかを下図にまとめました。
画像の表示
基本的なメソッドの定義方法は日付のフォーマットと同じです。
まずはメソッドの定義を確認してみます。object BindingAdapters { @BindingAdapter("imageUrl", "error") @JvmStatic fun loadImage(view:ImageView, url:String, drawable: Drawable){ Picasso.get().load(url).error(drawable).into(view) } }先ほどとの違いは@BindingAdapterアノテーションの値が2つになっています。
2つあると言うことはxmlでも2つオリジナル属性が生成されるということです。imageUrl属性はURLを指します。
error属性は、画像のダウンロードに失敗したときの画像をしていします。レイアウトのxmlを確認してみましょう。
<ImageView android:layout_width="0dp" android:layout_height="0dp" android:layout_margin="16dp" app:imageUrl="@{url}" ←ここが重要 app:error="@{@android:drawable/btn_dialog}" ←ここが重要 app:layout_constraintBottom_toTopOf="@+id/te app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent app:layout_constraintTop_toTopOf="parent" />レイアウトファイルに2つのオリジナル属性が存在することがわかります。
更にerror属性にはDrawableのidが指定されています。
しかし、メソッドの引数では Drawable が指定されています。
これはデータバインディングがいい具合にDrawable型に変換してくれます。
めちゃくちゃ便利ですね。今回はPicassoを使って画像を取得しています。失敗した場合の画像も同時に設定できるので、単純に便利です。
また、これまでの内容を図にまとめてみましたので、確認しておきます。
注意事項
最後に、重要な注意点を説明します。KotlinでBindingAdapterを定義するときは、必ず object をつかってください。companion objectでは動作しません。
すべてのソースコード
本記事で使用したサンプルコードの全容を載せておきます。
build.gradle
groovybuild.gradleapply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' android { compileSdkVersion 29 buildToolsVersion "29.0.2" defaultConfig { applicationId "jp.co.casareal" minSdkVersion 14 targetSdkVersion 29 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } dataBinding { enabled = true } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.core:core-ktx:1.2.0' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' implementation 'com.squareup.picasso:picasso:2.71828' }レイアウトリソースファイル
activity_main.xml<?xml version="1.0" encoding="utf-8"?> <layout 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"> <data> <import type="jp.co.casareal.BindingAdapters"/> <variable name="url" type="String" /> <variable name="now" type="java.util.Date" /> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <ImageView android:layout_width="0dp" android:layout_height="0dp" android:layout_margin="16dp" app:imageUrl="@{url}" app:error="@{@android:drawable/btn_dialog}" app:layout_constraintBottom_toTopOf="@+id/textView" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/textView" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginLeft="16dp" android:layout_marginEnd="16dp" android:layout_marginRight="16dp" android:layout_marginBottom="16dp" app:convertDate="@{now}" android:textAppearance="@style/TextAppearance.AppCompat.Large" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout>objectクラス
BindingAdapters.ktpackage jp.co.casareal import android.graphics.drawable.Drawable import android.widget.ImageView import android.widget.TextView import androidx.databinding.BindingAdapter import com.squareup.picasso.Picasso import java.text.SimpleDateFormat import java.util.* object BindingAdapters { @BindingAdapter("imageUrl", "error") @JvmStatic fun loadImage(view:ImageView, url:String, drawable: Drawable){ Picasso.get().load(url).error(drawable).into(view) } @BindingAdapter("convertDate") @JvmStatic fun convertDate(view:TextView, date:Date){ view.text = SimpleDateFormat("yyyy/MM/dd hh:mm:ss").format(date) } }Activity
MainActivity.ktpackage jp.co.casareal import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.databinding.DataBindingUtil import jp.co.casareal.databinding.ActivityMainBinding import java.util.* class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main) binding.url = "https://www.casareal.co.jp/common/images/logo01.png" binding.now = Date(System.currentTimeMillis()) binding.lifecycleOwner = this } }まとめ
BindingAdapterをうまく利用すればグルーコードが減って、処理も1元管理しつつ再利用ができるので便利!!!
積極的に利用していきたいですね。
- 投稿日:2021-01-08T15:24:05+09:00
動的にリソースIDを取得する2つの方法
静的に定義されたリソース ID をプログラム上から取得する方法です。
getIdentifier を使う
Resources クラスで定義されている
getIdentifier
を使います。Resources | Android Developers
たとえば、
strings.xml
内で、prefix_01
prefix_02
...prefix_10
のように文字列が連番で定義されているとします。これをプログラム上から動的に得るには、for (i in 1..10) { val id = resources.getIdentifier("prefix_${i.toString().padStart(2, '0')}", "string", packageName) }とします。
リフレクションを使う
リソースファイル内で定義されたリソース ID が連番になっていないなど、何らかの理由で上記の方法が使えない or 使いにくい場合は、リフレクションを使います。
たとえば、
strings.xml
内でprefix_
から始まる文字列のリソース ID を全て取得したい場合は、次のようにします。for (field in R.string::class.java.declaredFields) { if (Modifier.isStatic(field.modifiers) && !Modifier.isPrivate(field.modifiers) && field.type == Int::class.javaPrimitiveType) { try { if (field.name.startsWith("prefix_")) { val id = field.getInt(null) // do something } } catch (e: Exception) { e.printStackTrace() } } }異なるパッケージ内の
R
を参照する場合は、パッケージネームを直接指定する必要があります。com.example.R.string::class.java.declaredFields参考
文字列からリソースIDを取得する - Qiita
android - How to easily iterate over all strings within the "strings.xml" resource file? - Stack Overflow