- 投稿日:2021-01-22T23:38:29+09:00
SonarQube(Java)の、ルールの出典を確認したい
Whats'?
SonarQubeで使えるJavaのルールって、SonarQube自身が提供しているのか、それとも他のOSSなどのルールを使っているのか知りたいなぁと思いまして。
結論
すべて、SonarQube自前のルールでした。
SpotBugs、FindSecBugs、PMD、Checkstyleで検出した問題については、インポートが可能だということです。
SonarQube doesn't run your external analyzers or generate reports. It only imports pre-generated reports.
Importing SpotBugs, FindSecBugs, PMD, Checkstyle Issues Reports(※このページはもうメンテナンスされていません)
今回は、その確認の過程を書いていきます。
環境
今回は、SonarQubeサーバー 8.6 Community Editionを対象とします。
SonarQubeで使えるJavaのルール
SonarQubeサーバーで「Rules」から「Java」を選ぶと、Javaに関するルールが605あることが確認できます。
なんとなく、ドキュメントを見るとSpotBugs、FindBugs、FindSecBugs、PMD、Checkstyleが関係していそうな気はします。
External Issues、とは書いていますが…。
Analyzing Source Code / Languages / Java
SonarQube上で見てみても、関係しているのかどうかわかりません。
個別のルールを見ても、やっぱりわかりません。
分類として、Code Smell、Bug、Vulnerability、Security Hotspotがあるのはわかるんですけどね。
Sonar Maven Pluginを見る
こちらのエントリで少し使ってみたので、Sonar Maven Pluginを起点に追ってみましょう。
現時点のSonar Maven Pluginの
pom.xml
などを見ても、SpotBugsなどが入っている様子はありません。https://github.com/SonarSource/sonar-scanner-maven/blob/3.8.0.2131/pom.xml
どうやら、クライアント側は関係ないようです。
Sonar Java Pluginを見る
いろいろ調べていると、こちらが実体のように思います。
Code Quality and Security for Java
ここで、SonarQubeサーバーのインストールディレクトリにある、
lib/extensions
ディレクトリを見てみます。ちょっと気になります。
$ ll lib/extensions -rw-r--r--. 1 xxxxx xxxxx 5056472 Dec 9 10:25 sonar-csharp-plugin-8.15.0.24505.jar -rw-r--r--. 1 xxxxx xxxxx 10627704 Dec 9 10:25 sonar-css-plugin-1.3.1.1642.jar -rw-r--r--. 1 xxxxx xxxxx 1550044 Dec 9 10:25 sonar-flex-plugin-2.6.0.2294.jar -rw-r--r--. 1 xxxxx xxxxx 6102054 Dec 9 10:25 sonar-go-plugin-1.8.1.1804.jar -rw-r--r--. 1 xxxxx xxxxx 823033 Dec 9 10:25 sonar-html-plugin-3.3.0.2534.jar -rw-r--r--. 1 xxxxx xxxxx 19648 Dec 9 10:25 sonar-jacoco-plugin-1.1.0.898.jar -rw-r--r--. 1 xxxxx xxxxx 18717478 Dec 9 10:25 sonar-java-plugin-6.9.0.23563.jar -rw-r--r--. 1 xxxxx xxxxx 19371396 Dec 9 10:25 sonar-javascript-plugin-7.0.1.14561.jar -rw-r--r--. 1 xxxxx xxxxx 8466805 Dec 9 10:25 sonar-kotlin-plugin-1.8.1.1804.jar -rw-r--r--. 1 xxxxx xxxxx 5310930 Dec 9 10:25 sonar-php-plugin-3.13.0.6849.jar -rw-r--r--. 1 xxxxx xxxxx 4527459 Dec 9 10:25 sonar-python-plugin-3.1.0.7619.jar -rw-r--r--. 1 xxxxx xxxxx 13053290 Dec 9 10:25 sonar-ruby-plugin-1.8.1.1804.jar -rw-r--r--. 1 xxxxx xxxxx 14687152 Dec 9 10:25 sonar-scala-plugin-1.8.1.1804.jar -rw-r--r--. 1 xxxxx xxxxx 3760426 Dec 9 10:25 sonar-vbnet-plugin-8.15.0.24505.jar -rw-r--r--. 1 xxxxx xxxxx 2242738 Dec 9 10:25 sonar-xml-plugin-2.0.1.2020.jarCommunity Editionを使っているので、ちょうどサポート対象の15言語分ですね。
SonarQubeサーバーのドキュメントを見ていると、解析はクライアント側でやっているのかな?と思うのですが、
適用するルールはサーバー側で管理しているので、ちょっとわからなくなりますね。
sonar-java-plugin-6.9.0.23563.jar
は、こちらのリポジトリから作られたもののようです。Code Quality and Security for Java
ちょっと中身を見てみましょう。
$ jar -tvf lib/extensions/sonar-java-plugin-6.9.0.23563.jar | grep -iE 'spotbugs|findbugs|findsecbugs|pmd|checkstyle' 1626 Mon Oct 05 01:32:58 UTC 2020 org/sonar/java/checks/NoCheckstyleTagPresenceCheck.class 1587 Mon Oct 05 01:32:58 UTC 2020 org/sonar/java/checks/NoPmdTagPresenceCheck.class 4548 Mon Oct 05 01:32:58 UTC 2020 org/sonar/java/externalreport/PmdSensor.class 6057 Mon Oct 05 01:32:58 UTC 2020 org/sonar/java/externalreport/CheckstyleXmlReportReader.class 9209 Mon Oct 05 01:32:58 UTC 2020 org/sonar/java/externalreport/PmdXmlReportReader.class 5807 Mon Oct 05 01:32:58 UTC 2020 org/sonar/java/externalreport/SpotBugsSensor.class 511 Mon Oct 05 01:32:58 UTC 2020 org/sonar/java/externalreport/CheckstyleXmlReportReader$IssueConsumer.class 9549 Mon Oct 05 01:32:58 UTC 2020 org/sonar/java/externalreport/SpotBugsXmlReportReader.class 0 Mon Oct 05 01:32:58 UTC 2020 org/sonar/l10n/java/rules/spotbugs/ 24823 Mon Oct 05 01:32:06 UTC 2020 org/sonar/l10n/java/rules/spotbugs/findsecbugs-rules.json 76105 Mon Oct 05 01:32:06 UTC 2020 org/sonar/l10n/java/rules/spotbugs/fbcontrib-rules.json 0 Mon Oct 05 01:32:58 UTC 2020 org/sonar/l10n/java/rules/checkstyle/ 34405 Mon Oct 05 01:32:06 UTC 2020 org/sonar/l10n/java/rules/checkstyle/rules.json 5829 Mon Oct 05 01:32:58 UTC 2020 org/sonar/java/externalreport/CheckstyleSensor.class 0 Mon Oct 05 01:32:58 UTC 2020 org/sonar/l10n/java/rules/pmd/ 61670 Mon Oct 05 01:32:06 UTC 2020 org/sonar/l10n/java/rules/pmd/rules.json 130757 Mon Oct 05 01:32:06 UTC 2020 org/sonar/l10n/java/rules/spotbugs/spotbugs-rules.json 0 Mon Oct 05 01:32:58 UTC 2020 META-INF/maven/com.google.code.findbugs/ 0 Mon Oct 05 01:32:58 UTC 2020 META-INF/maven/com.google.code.findbugs/jsr305/ 4286 Fri Mar 31 10:21:36 UTC 2017 META-INF/maven/com.google.code.findbugs/jsr305/pom.xml 121 Fri Mar 31 10:55:32 UTC 2017 META-INF/maven/com.google.code.findbugs/jsr305/pom.properties少し関係ありそうですね。
たとえば、こちら。
抜粋してみます。
[ { "key": "AM_CREATES_EMPTY_JAR_FILE_ENTRY", "name": "Bad practice - Creates an empty jar file entry", "type": "CODE_SMELL", "severity": "MAJOR", "url": "https://spotbugs.readthedocs.io/en/latest/bugDescriptions.html#AM_CREATES_EMPTY_JAR_FILE_ENTRY" }, { "key": "AM_CREATES_EMPTY_ZIP_FILE_ENTRY", "name": "Bad practice - Creates an empty zip file entry", "type": "CODE_SMELL", "severity": "MAJOR", "url": "https://spotbugs.readthedocs.io/en/latest/bugDescriptions.html#AM_CREATES_EMPTY_ZIP_FILE_ENTRY" }, { "key": "AT_OPERATION_SEQUENCE_ON_CONCURRENT_ABSTRACTION", "name": "Multi-threading - Sequence of calls to concurrent abstraction may not be atomic", "type": "BUG", "severity": "MAJOR", "url": "https://spotbugs.readthedocs.io/en/latest/bugDescriptions.html#AT_OPERATION_SEQUENCE_ON_CONCURRENT_ABSTRACTION" }, ... ]
type
とルール上の分類が一致しそうですね。リポジトリを
clone
して中身を見てみます。$ git clone https://github.com/SonarSource/sonar-java.git $ cd sonar-java対象のバージョンに
checkout
。$ git checkout 6.9.0.23563
type
で集計してみます。$ find external-reports/src/main/resources/org/sonar/l10n/java/rules -name '*.json' | xargs grep '"type"' | perl -wp -e 's!.+("type.+)!$1!' | sort | uniq -c 553 "type": "BUG", 654 "type": "CODE_SMELL", 139 "type": "VULNERABILITY",多い…。
ファイルパスも一部含めてみましょう。
$ find external-reports/src/main/resources/org/sonar/l10n/java/rules -name '*.json' | xargs grep '"type"' | perl -wp -e 's!.+/(.+/.+) *("type.+)!$1 $2!' | sort | uniq -c 156 checkstyle/rules.json: "type": "CODE_SMELL", 288 pmd/rules.json: "type": "CODE_SMELL", 308 spotbugs/fbcontrib-rules.json: "type": "BUG", 128 spotbugs/findsecbugs-rules.json: "type": "VULNERABILITY", 245 spotbugs/spotbugs-rules.json: "type": "BUG", 210 spotbugs/spotbugs-rules.json: "type": "CODE_SMELL", 11 spotbugs/spotbugs-rules.json: "type": "VULNERABILITY",うーん、よくわからないですね…。
実際にSonarQubeサーバーが認識しているルールは、ここにあるようです。
Sonar wayプロファイルの定義もありました。
なんですけど、全然わからない名前が並んでいます。
{ "name": "Sonar way", "ruleKeys": [ "S100", "S101", "S106", "S107", "S108", "S110", "S112", "S114", ...このディレクトリ内にあるファイルのほとんどは、自動生成されたもののようです。
実体は、同じプロジェクトのこういうコードでしょうか?
@DeprecatedRuleKey(ruleKey = "S00100", repositoryKey = "squid") @Rule(key = "S100") public class BadMethodNameCheck extends IssuableSubscriptionVisitor {
S[数字]
なものを確認してみましょう。$ grep -rE '"S[0-9]+"' java-checks/src/main/java | perl -wp -e 's!.+(S\d+).+!$1!' | sort -u | wc -l 599599個ですね。
ここで、SonarQubeのルールを"リポジトリ"という単位別に見れることに気づいたので、見比べてみます。
「SonarQube」が599個。一致します。
つまり、ここにあるのがSonarQubeのルールの実体です。
残りの6個はというと、こちらに定義してあります。
つまり、605個のルールはすべてSonarQube自前のルールでした。
ここでこのページに気づき、あくまでSpotBugs、FindSecBugs、PMD、Checkstyleは「インポート可能なもの」だと認識しました…。
Importing SpotBugs, FindSecBugs, PMD, Checkstyle Issues Reports
なるほど、それで"External Issues"だったんですね。
こちらのページで、インポートするファイルのパスをシステムプロパティで指定できると書いていますが、それが書いてあるのがここですね。
なるほど、すっきりしました。
- 投稿日:2021-01-22T23:17:11+09:00
初心者が1から始めるJava開発-Vol.5- 繰り返し編
前回に引き続き今回はJavaの繰り返しについて書いていこうと思います。
前回のVol.4も重要なので、初心者は要チェックです。
Javaの初心者向けおすすめ本
スッキリわかるJava入門 第3版 (スッキリシリーズ)
繰り返し
繰り返しとは、そのままですが「何度も処理を繰り返す」ことを言います。
プログラムの世界では超頻出です。例えば
- 同じ数式で全社員の給与計算をする
- 動画を再生し終えたら、次の動画を自動再生する
- 100ページのPDFとして100人分の社員情報を印字する etc
など、他にも山ほどあると思いますが、これらも何らかの処理を複数回実行するために、
この「繰り返し」処理を実行しています。では実際にコードで書いていきましょう。
前回のあいさつのプログラムを使います。
プログラム実装
Greeting.javaを開きましょう。
まず、greetingIfというメソッドを作り、ifの部分をメソッド化しておきましょう。
ifからgreetingSwitchの直前までをctrlキーを押しながらマウスでスクロールし、
if文の部分を選択したら、右クリックでRefactor > Extract Methodと選択してください。Method Name:にはgreetingIfと入れてOKを押しましょう。
すると、eclipseが勝手にメソッドを作成してくれたと思います。
これを「リファクタリング」「リファクタ」と言ったりします。
代替の場合は動作(Input/Output)を変えずにコードを書き換えることを言います。
エンジニアやプログラマの現場では頻出単語なので、覚えましょう。package JavaTutorial; public class Greeting { public static void main(String[] args) { String timeZone = "朝"; greetingIf(timeZone); greetingSwitch(timeZone); } private static void greetingIf(String timeZone) { if (timeZone.equals("朝")) { System.out.println("Good Morning World!"); } else if (timeZone.equals("昼")) { System.out.println("Hello World!"); } else if (timeZone.equals("夜")) { System.out.println("Good Evening World!"); } } private static void greetingSwitch(String timeZone) { switch (timeZone) { case "朝": System.out.println("Good Morning World!"); break; case "昼": System.out.println("Hello World!"); break; case "夜": System.out.println("Good Evening World!"); break; } } }全体がこんな感じになっていればOKです。
続いて繰り返し処理を書いていきましょう。
繰り返し処理を書くときはforを使います。
mainメソッドのtimeZoneの次の行にforとタイプしてみてください。
for - use index for arrayというやつを選びます。すると
for (int i = 0; i < args.length; i++) { }こんな奴が出てきたと思います。これが一番基本的なfor文です。
日本語訳すると、
i を0からargsの長さまで繰り返し実行する、その時1回実行毎に iは1ずつ増加していく
という感じです。今回はargsの中には何も持っていないので、args.lengthの部分を3に書き換えましょう。
for (int i = 0; i < 3; i++) { }こうなります。
で、最後に、forの中にgreetingIfとgreetingswitchを入れて見ましょう。public static void main(String[] args) { String timeZone = "朝"; for (int i = 0; i < 3; i++) { greetingIf(timeZone); greetingSwitch(timeZone); } }メインクラスはこのようになります。
では実行してみましょう
Good Morning World! Good Morning World! Good Morning World! Good Morning World! Good Morning World! Good Morning World!のように6回出てくれば成功です。
アレンジ
これでは面白くないので
iが何番のときなのかを把握するためにforの中でiを使ってみましょう。System.out.println(i + 1 + "周目");というコードをforのすぐ後ろに書いてみましょう。
public static void main(String[] args) { String timeZone = "朝"; for (int i = 0; i < 3; i++) { System.out.println(i + 1 + "周目"); greetingIf(timeZone); greetingSwitch(timeZone); } }こんな感じで実行してみてください。
1周目 Good Morning World! Good Morning World! 2周目 Good Morning World! Good Morning World! 3周目 Good Morning World! Good Morning World!こうなります。確かにiが1個ずつ増えて3週回ったら終わっていることがわかるかと思います。
さらにアレンジ
今度は1週目は朝、2週目は昼、3週目は夜となるようにしていきます。
String[] timeZones= {"朝","昼","夜"}; String timeZone = "朝";のように、上にtimeZonesという配列を宣言してください。
そして、for文の3としたところをtimeZones.lengthに書き換えます。public static void main(String[] args) { String[] timeZones = { "朝", "昼", "夜" }; String timeZone = "朝"; for (int i = 0; i < timeZones.length; i++) { System.out.println(i + 1 + "周目"); greetingIf(timeZone); greetingSwitch(timeZone); } }最後にforのすぐ後ろに
String timeZone = timeZones[i];としてもともとの
String timeZone = "朝";は消しましょう。
public static void main(String[] args) { // 配列を宣言 String[] timeZones = { "朝", "昼", "夜" }; // timeZones.lengthまで回す for (int i = 0; i < timeZones.length; i++) { // timeZonesの中身のi番目を取得する(配列は1番目が0、2番目が1、・・・となります) String timeZone = timeZones[i]; // 配列から取得したtimeZoneで挨拶を出力していく System.out.println(i + 1 + "周目"); greetingIf(timeZone); greetingSwitch(timeZone); } }こんな感じになります。
では実行してみましょう。
1周目 Good Morning World! Good Morning World! 2周目 Hello World! Hello World! 3周目 Good Evening World! Good Evening World!こんな感じになっていれば完璧です。
最後に
for文には様々な書き方があります。拡張for文、streamなど。
また、forだけでなくwhile文というものもあります。兎に角書き方が豊富である=必要な場面が多い
という事なので、ぜひ今回のコードが他に書きようないかいろいろ試してみてください。
- 投稿日:2021-01-22T22:31:28+09:00
初心者が1から始めるJava開発-Vol.4- 条件分岐編
プログラムには大きく3要素あると思っていて
- 順次実行(順次)
- 条件分岐(分岐)
- 繰り返し(反復)
です。
このうち順次というのは読んで字のごとく、「順番に処理を実行する」という意味で、
Javaでは、上に書いたものから先に実行されるという一般的なルールに従います。当然、Vol.3で紹介したメソッドやクラスは呼び出された順に実行するので、
クラス内に書かれている順ではなく、呼び出された順です。今回は2つ目の条件分岐について紹介していきます。
Javaの初心者向けおすすめ本
スッキリわかるJava入門 第3版 (スッキリシリーズ)
条件分岐
読んで字のごとく、何らかの条件に従い処理を分岐させることです。
分かりやすい例をあげましょう(いい例かは別ですw)
子供が生まれて、
1、もし男の子だったら「たかし」という名前を付ける
2、もし女の子だったら「さやか」という名前を付けるというように性別という「条件」によって、おなじ「名前を付ける」でも、行動が変わります。
というように、あらかじめ条件が存在し、それによって処理される内容も変わるというのが条件分岐で、Javaでも当然そのようなことが実現できます。
ではまず、簡単な例として、あいさつのプログラムを書いていきましょう。
プログラム実装
仕様 「朝」だったら「Good Morning World!」 「昼」だったら「Hallo World!」 「夜」だったら「Good Evening World!」 と出力するプログラムでは実際に、書いていきましょう。
まず、「Greeting.java」というクラスを作ります。パッケージはどこでも構いません。
やり方わすれた方は、Vol.2に戻って確認してください。package JavaTutorial; public class Greeting{ }こんな感じでクラスが作成されましたでしょうか?
packageの部分は作った階層によって異なるので、いまは特に考えなくてよいです(本当は重要です)。
次に出力するメソッドを実装します。Vol.2同様、まずは汎用的な挨拶であるHello World!を表示するように実装しましょう。
package JavaTutorial; public class Greeting { public static void main(String[] args) { System.out.println("Hello World!"); } }こんな感じでした。
それではいよいよ、条件分岐の実装をしていきます。
Javaでは条件分岐に使う文法は2つ覚えてください。
- if文
- switch文
それぞれ説明していきます。
if文
文字通りifの後ろに定義する条件に合致すれば実行し、合致しなければ実行しないというものです。
実際の実装をしつつ説明していきます。まず、System.out.println("Hello World!");の前にifとタイプしてみましょう。
すると、if-if statementとサジェストされるのでそれを選びましょう。
package JavaTutorial; public class Greeting { public static void main(String[] args) { if (condition) { } System.out.println("Hello World!"); } }こんな感じになりましたでしょうか?
package JavaTutorial; public class Greeting { public static void main(String[] args) { if (condition) { //conditionの部分の条件に合致したらこの部分の処理を実行する } System.out.println("Hello World!"); } }それでは、「朝」だったら「Good Morning World!」と表示する仕様を書いていきましょう
package JavaTutorial; public class Greeting { public static void main(String[] args) { String timeZone = "朝"; if (timeZone.equals("朝")) { System.out.println("Good Morning World!"); } System.out.println("Hello World!"); } }timeZoneというString型のローカル変数を定義してこいつの状態によって挨拶を返すようにしてみましょう。
これでRunAsからJavaApplicationで実行してみましょう。。。
2つ出てきましたね。。
そうです。Hello Worldは無条件に実行されます。
なので、実行されないようにしましょう。
以下のようにelseを使えばOKです。package JavaTutorial; public class Greeting { public static void main(String[] args) { String timeZone = "朝"; if (timeZone.equals("朝")) { System.out.println("Good Morning World!"); } else { System.out.println("Hello World!"); } } }このようにelseの中に入れてしまえば、
if 朝なら GoodMorning
ではなければ Helloという意味になります。
これでRunAsからJavaApplicationで実行してみましょう。
確かにGood Morning World!だけが出力されたと思います。次に
timeZoneを「昼」に書き換えて実行してみましょう。今度は逆にHello Worldだけが出力されたと思います。
では最後に
「昼」だったら「Hallo World!」
「夜」だったら「Good Evening World!」
の仕様を完成させましょう。3個の条件判定をする場合にはelse ifを使って2番目以降の条件を書いていくことが可能です。
package JavaTutorial; public class Greeting { public static void main(String[] args) { String timeZone = "朝"; if (timeZone.equals("朝")) { System.out.println("Good Morning World!"); } else if (timeZone.equals("昼")) { System.out.println("Hello World!"); } else if (timeZone.equals("夜")) { System.out.println("Good Evening World!"); } } }こんな感じで書くことができます。
ちなみにこう書くと「朝」でも「昼」でも「夜」でもない場合は挨拶をしません。
※皆さんはしましょう。if文が分かったところで、今度はこれをswitch文で書き換えていこうと思います。
switch文
switch文は、if文よりも数が多い条件判定のときに役立ちます。
また決まった番号やIDが振られているときなどに重宝します。どっちで書いても処理の内容はほとんど変わらないので、覚えましょう。
Vol.3で学んだメソッドに切り出して書いてみましょう。
名前は何でも良いですがgreetingSwitchとでもしましょうか。
private static void greetingSwitch() { }private メソッドにして、staticも付けておきましょう。
そして、mainメソッドから呼びます。timeZoneは同じものを使えるように引数に指定します。
こんな感じです。package JavaTutorial; public class Greeting { public static void main(String[] args) { String timeZone = "朝"; if (timeZone.equals("朝")) { System.out.println("Good Morning World!"); } else if (timeZone.equals("昼")) { System.out.println("Hello World!"); } else if (timeZone.equals("夜")) { System.out.println("Good Evening World!"); } greetingSwitch(timeZone); } private static void greetingSwitch(String timeZone) { } }そしたら実際にswitch文を書いていきます。greetingSwitchメソッドの中で「switch」とタイプしてください。
switch-switch case statementとサジェストされたものがあると思うのでそれを選びます。
すると、private static void greetingSwitch(String timeZone) { switch (key) { case value: break; default: break; } }こんな感じになったと思います。
switch文では
「keyがvalueだったら直下の処理を実行する」
という意味の記述になります。なのでifの内容をswitchにすると以下のように書けます。
private static void greetingSwitch(String timeZone) { switch (timeZone) { case "朝": System.out.println("Good Morning World!"); break; case "昼": System.out.println("Hello World!"); break; case "夜": System.out.println("Good Evening World!"); break; default: break; } }こんな感じになります。
- default 部分は何も処理がない場合は無くてもOKです。
- breakを書かなかった場合に、場合によっては他の処理も実行されてしまう可能性があるので、breakを書きます。
後は実際に事項してみてください。
Helloのあいさつのやり取りのように2本同じものが出てきたと思います。
このように、入力によって処理を分岐させるのが「条件分岐」であり、javaでは多くの場合湖のように書きます。
最後に
条件分岐はJavaに限らずどんなプログラミング言語でも重要な考え方になりますので、マスターして使いこなしましょう。
- 投稿日:2021-01-22T21:30:26+09:00
MavenプロジェクトでSonarQubeを使ってみる
What's?
こちらのエントリでSonarQubeサーバーのインストール方法はわかったので、今後はSonarQubeを動かしてみましょう。
CentOS 8にSonarQubeサーバーをインストールする
Java(Maven)なプロジェクトで、SonarQubeのソースコード解析を動かしてみます。
環境
SonarQubeサーバーのバージョンは
8.6.0.39681
、SonarQubeサーバーが稼働しているサーバーのIPアドレスは192.168.33.10
とします。JavaやMavenのバージョンはこちら。
$ java --version openjdk 11.0.9.1 2020-11-04 OpenJDK Runtime Environment (build 11.0.9.1+1-Ubuntu-0ubuntu1.20.04) OpenJDK 64-Bit Server VM (build 11.0.9.1+1-Ubuntu-0ubuntu1.20.04, mixed mode, sharing) $ mvn --version Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 11.0.9.1, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "5.4.0-64-generic", arch: "amd64", family: "unix"サンプルプロジェクト
ソースコード解析をしようにも、ソースコードがないとどうにもなりません。
今回は、すでに存在するものを引っ張ってくることにしました。Spring PetClinicです。
Spring PetClinic Sample Application
cloneしてきて
$ git clone https://github.com/spring-projects/spring-petclinic.git $ cd spring-petclinic特定のタグにcheckoutしたかったところですが、タグがなかったのでコミットにしておきました。
$ git log -1 commit 02babdd8cb0d494d043fdf35a24dd2127c5d5167 (HEAD -> main, origin/main, origin/HEAD) Author: Stephane Nicoll <snicoll@vmware.com> Date: Fri Dec 25 13:02:48 2020 +0100 Upgrade to Spring Boot 2.4.1 $ git checkout 02babdd8cb0d494d043fdf35a24dd2127c5d5167これで、ソースコードを解析するプロジェクトの準備は完了です。
SonarQubeでソースコードを解析する
SonarQubeサーバー側では、ユーザーとプロジェクトを作成しておきます。
プロジェクト作成時に
トークンを作って
ビルドシステムや言語を選ぶと、使い方を教えてくれます。
今回は、プロジェクト名は
sonar-spring-petclinic
としました。
※キャプチャとは違う名前ですね…以下のように、システムプロパティでSonarQubeサーバー上のプロジェクト名、接続先、トークンを指定して
sonar:sonar
ゴールを実行します。$ mvn compile sonar:sonar \ -Dsonar.projectKey=sonar-spring-petclinic \ -Dsonar.host.url=http://192.168.33.10:9000 \ -Dsonar.login=[SonarQubeトークン]この時、ソースコードのビルドは完了している必要があります。
Sonar Maven Plugin実行時の様子です。
[INFO] --- sonar-maven-plugin:3.8.0.2131:sonar (default-cli) @ spring-petclinic --- [INFO] User cache: $HOME/.sonar/cache [INFO] SonarQube version: 8.6.0 [INFO] Default locale: "ja_JP", source code encoding: "UTF-8" [INFO] Load global settings [INFO] Load global settings (done) | time=216ms [INFO] Server id: 641C58CB-AXcoJzcTxqy5V9PiCfw8 [INFO] User cache: $HOME/.sonar/cache [INFO] Load/download plugins [INFO] Load plugins index [INFO] Load plugins index (done) | time=66ms [INFO] Load/download plugins (done) | time=105ms [INFO] Process project properties [INFO] Process project properties (done) | time=7ms [INFO] Execute project builders [INFO] Execute project builders (done) | time=1ms [INFO] Project key: sonar-spring-petclinic [INFO] Base dir: /path/to/spring-petclinic [INFO] Working dir: /path/to/spring-petclinic/target/sonar [INFO] Load project settings for component key: 'sonar-spring-petclinic' [INFO] Load project settings for component key: 'sonar-spring-petclinic' (done) | time=16ms [INFO] Load quality profiles [INFO] Load quality profiles (done) | time=44ms [INFO] Load active rules [INFO] Load active rules (done) | time=798ms [INFO] Indexing files... [INFO] Project configuration: [INFO] 37 files indexed [INFO] 0 files ignored because of scm ignore settings [INFO] Quality profile for java: My Sonar way [INFO] Quality profile for xml: Sonar way [INFO] ------------- Run sensors on module petclinic [INFO] Load metrics repository [INFO] Load metrics repository (done) | time=19ms [INFO] Sensor JavaSquidSensor [java] [INFO] Configured Java source version (sonar.java.source): 8 [INFO] JavaClasspath initialization [INFO] JavaClasspath initialization (done) | time=20ms [INFO] JavaTestClasspath initialization [INFO] JavaTestClasspath initialization (done) | time=4ms [INFO] Java Main Files AST scan [INFO] 25 source files to be analyzed [INFO] Load project repositories [INFO] Load project repositories (done) | time=20ms [INFO] 25/25 source files have been analyzed [INFO] Java Main Files AST scan (done) | time=2436ms [INFO] Java Test Files AST scan [INFO] 11 source files to be analyzed [INFO] Java Test Files AST scan (done) | time=710ms [INFO] 11/11 source files have been analyzed [INFO] Java Generated Files AST scan [INFO] 0 source files to be analyzed [INFO] Java Generated Files AST scan (done) | time=1ms [INFO] 0/0 source files have been analyzed [INFO] Sensor JavaSquidSensor [java] (done) | time=3399ms [INFO] Sensor CSS Rules [cssfamily] [INFO] No CSS, PHP, HTML or VueJS files are found in the project. CSS analysis is skipped. [INFO] Sensor CSS Rules [cssfamily] (done) | time=1ms [INFO] Sensor JaCoCo XML Report Importer [jacoco] [INFO] 'sonar.coverage.jacoco.xmlReportPaths' is not defined. Using default locations: target/site/jacoco/jacoco.xml,target/site/jacoco-it/jacoco.xml,build/reports/jacoco/test/jacocoTestReport.xml [INFO] No report imported, no coverage information will be imported by JaCoCo XML Report Importer [INFO] Sensor JaCoCo XML Report Importer [jacoco] (done) | time=7ms [INFO] Sensor C# Properties [csharp] [INFO] Sensor C# Properties [csharp] (done) | time=1ms [INFO] Sensor SurefireSensor [java] [INFO] parsing [/path/to/spring-petclinic/target/surefire-reports] [INFO] Sensor SurefireSensor [java] (done) | time=2ms [INFO] Sensor JavaXmlSensor [java] [INFO] 1 source files to be analyzed [INFO] Sensor JavaXmlSensor [java] (done) | time=219ms [INFO] 1/1 source files have been analyzed [INFO] Sensor HTML [web] [INFO] Sensor HTML [web] (done) | time=3ms [INFO] Sensor XML Sensor [xml] [INFO] 1 source files to be analyzed [INFO] Sensor XML Sensor [xml] (done) | time=123ms [INFO] 1/1 source files have been analyzed [INFO] Sensor VB.NET Properties [vbnet] [INFO] Sensor VB.NET Properties [vbnet] (done) | time=1ms [INFO] ------------- Run sensors on project [INFO] Sensor Zero Coverage Sensor [INFO] Sensor Zero Coverage Sensor (done) | time=11ms [INFO] Sensor Java CPD Block Indexer [INFO] Sensor Java CPD Block Indexer (done) | time=56ms [INFO] SCM Publisher SCM provider for this project is: git [INFO] SCM Publisher 1 source file to be analyzed [INFO] SCM Publisher 0/1 source files have been analyzed (done) | time=286ms [WARNING] Missing blame information for the following files: [WARNING] * pom.xml [WARNING] This may lead to missing/broken features in SonarQube [INFO] CPD Executor 12 files had no CPD blocks [INFO] CPD Executor Calculating CPD for 13 files [INFO] CPD Executor CPD calculation finished (done) | time=11ms [INFO] Analysis report generated in 71ms, dir size=251 KB [INFO] Analysis report compressed in 95ms, zip size=112 KB [INFO] Analysis report uploaded in 82ms [INFO] ANALYSIS SUCCESSFUL, you can browse http://192.168.33.10:9000/dashboard?id=sonar-spring-petclinic [INFO] Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report [INFO] More about the report processing at http://192.168.33.10:9000/api/ce/task?id=AXcqAWMeAa54pbha1y28 [INFO] Analysis total time: 7.170 s [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 15.828 s [INFO] Finished at: 2021-01-22T21:11:10+09:00 [INFO] ------------------------------------------------------------------------SonarQubeサーバーへの送信が上手くいったら、プロジェクトのページを見てみます。
こんな感じになっています。
検出された問題の一覧が見れたり
指摘箇所が見れたり
ソースコードも見れたりします。
ということは、プロジェクトのソースコードはSonarQubeサーバーに送っているんですね。
Sonar Maven Pluginをpom.xmlに組み込む
先ほどは
mvn
実行時のシステムプロパティですべての設定を行い、またプラグインのバージョン指定などもなかったので、今度はpom.xml
にSonar Maven Pluginを組み込んでみましょう。こちらですね。
シンプルに、こんな感じになります。設定項目はほぼありません(スキップの指定があるくらいです)。
<build> <plugins> <plugin> <groupId>org.sonarsource.scanner.maven</groupId> <artifactId>sonar-maven-plugin</artifactId> <version>3.8.0.2131</version> </plugin> ... </plugins> </build>設定がないと書きましたが、先ほどのSonarQubeサーバーのURLなどの説明は?というと、こちらにあります。
これらは、
pom.xml
にプロパティとして指定しましょう。<properties> <!-- SonarQube --> <sonar.projectKey>sonar-spring-petclinic</sonar.projectKey> <sonar.host.url>http://192.168.33.10:9000</sonar.host.url> <sonar.login>dummy</sonar.login> ... </properties>トークンだけは、システムプロパティで実行時に渡すことにしました。
$ mvn sonar:sonar -Dsonar.login=[トークン]実際には、ソースコードのビルドとともに動かすことになると思うので、以下のような使い方になるでしょう。
$ mvn compile sonar:sonar -Dsonar.login=[トークン] $ mvn test sonar:sonar -Dsonar.login=[トークン] $ mvn package sonar:sonar -Dsonar.login=[トークン]ちなみに、Spring PetClinicの場合、HTTPでSonarQubeへの接続先を書いていたらCheckStyleでNGになりました…。
[INFO] --- maven-checkstyle-plugin:3.1.1:check (nohttp-checkstyle-validation) @ spring-petclinic --- [INFO] There is 1 error reported by Checkstyle 8.32 with src/checkstyle/nohttp-checkstyle.xml ruleset. [ERROR] pom.xml:[36,22] (extension) NoHttp: http:// URLs are not allowed but got 'http://192.168.33.10:9000'. Use https:// instead.今回は、コメントアウトしておきます…。
src/checkstyle/nohttp-checkstyle.xml<?xml version="1.0"?> <!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.2//EN" "https://checkstyle.org/dtds/configuration_1_2.dtd"> <module name="com.puppycrawl.tools.checkstyle.Checker"> <!-- <module name="io.spring.nohttp.checkstyle.check.NoHttpCheck"/> --> </module>適用しているルールについて
今回はなにも考えずに実行しましたが、デフォルトのルールでソースコードチェックされていることになります。
Project Informationで見た時の、「Quality Profile」というものが適用しているルールをまとめたプロファイルです。
デフォルトでは「Sonar way」というプロファイルが使われています。
各言語の全ルールが有効になっているわけではなく、ある程度は選別されているようです。
一覧はこちらです。
Sonar wayはビルトインプロファイルなので、編集することができません。
Sonar wayをベースにカスタマイズするには、コピーして新しいプロファイルを作って行うのがよいのでしょう。
こちらは、Sonar wayプロファイルをコピーして作成した「My Sonar way」というプロファイルです。
Deactivate
が押せるようになっています。あとは、プロジェクト側のQuality Profiliesで
作成したプロファイルを選択すると、次回の解析から使用されます。
これで、SonarQubeでの解析が始められますね。
- 投稿日:2021-01-22T20:24:30+09:00
【Android / Java】双方向 DataBinding + Clickイベント サンプルアプリ
はじめに
実務案件で DataBinding を利用している。
なかなか難しい。そこで双方向 DataBinding を用いたサンプルアプリを作成しながら DataBinding について学んだ。※ 今回は Java ですが、 Kotlin でも同様のものを作成してみようと思っております。その時は改めて投稿します。
サンプルアプリの概要
実装していることとしては
- フォーム入力時
- 入力されたテキストをフォーム下部にリアルタイムで表示
- ボタンアクティブにする(テキストの有無で切り替え)
- ボタンクリック時
- フォーム入力テキストを上部に表示
- フォームおよび下部のテキストを空欄に戻す(初期化する)
開発環境
実装
DataBinding導入
build.gradle
に記述を追加して DataBindingを利用できるようにする。app/build.gradleplugins { id 'com.android.application' } android { compileSdkVersion 30 buildToolsVersion "30.0.3" defaultConfig { applicationId "com.android.bidirectionaldatabindingsample" minSdkVersion 24 targetSdkVersion 30 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 } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'com.google.android.material:material:1.2.1' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' testImplementation 'junit:junit:4.+' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' }レイアウト要素をセット
まずは各要素には DataBinding の記述無しの状態で xml にのレイアウトを作成。
※
<layout></layout>
だけは先に記述。
このようにレイアウトのルート要素に<layout>
でくくったレイアウトファイルがあると、自動的にxmlファイル名に応じたBindingクラスが作られる。
今回: activity_main.xml => ActivityMainBindingactivity_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" > <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/click_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintVertical_bias="0.15" /> <EditText android:id="@+id/input_form" android:layout_width="184dp" android:layout_height="wrap_content" android:hint="テキストを入力" android:inputType="text" android:textSize="20dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@id/button" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintVertical_bias="0.3" /> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="ボタン" app:layout_constraintStart_toEndOf="@id/input_form" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="@id/input_form" /> <TextView android:id="@+id/realTimeText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20dp" app:layout_constraintStart_toStartOf="@id/input_form" app:layout_constraintTop_toBottomOf="@id/input_form" android:layout_marginTop="16dp" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout>DataBinding のロジック定義をするViewModelを作成する
ViewModel.java// BaseObservableクラスを継承 public class ViewModel extends BaseObservable { // ここにロジックを書いていく }MainActivityでViewModelをバインドする(紐づける)
MainActivity.javapublic class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); setTitle("双方向データバインディングサンプル"); // Bindingインスタンスを生成 ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main); // ViewModelオブジェクトとバインドする(紐付ける) ViewModel viewModel = new ViewModel(); binding.setViewModel(viewModel); } }レイアウトファイルでViewModelオブジェクトを利用できるようにする
レイアウトファイルに記述を追加
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" > <data> <!-- viewModelという名前でViewModelオブジェクトを登録 --> <variable name="viewModel" type="com.android.bidirectionaldatabindingsample.ViewModel" /> </data> <!-- ・・・ 省略 ・・・ -->準備が整ったのでこれから機能を実装していく。
フォーム入力でボタンアクティブにする
ViewModel
以下を記述
formText
getFormText()
setFormtext()
isButtonEnable()
ViewModel.javapublic class ViewModel extends BaseObservable { // 代入せずにnullでも良いが今回は "" を初期値とする。 private String formText = ""; // フォーム(EditText)の入力値の getter @Bindable public String getFormText() { return formText; } // フォーム(EditText)の入力値の setter public void setFormText(String formText) { this.formText = formText; // この記述でisButtonEnable()が呼ばれる notifyPropertyChanged(BR.buttonEnable); } // フォーム(EditText)へのテキスト入力有無で、ボタン活性・非活性を制御するフラグの getter @Bindable public boolean isButtonEnable() { // 入力あり:true 入力なし:false return !TextUtils.isEmpty(formText); } }レイアウト
以下を記述
- フォーム要素に
android:text="@={viewModel.formText}"
- ボタン要素に
android:enabled="@{viewModel.buttonEnable}"
activity_main.xml<!-- ・・・ 省略 ・・・ --> <!-- 「@={}」で双方向のバインディング --> <!-- ViewModelの getFormText() と setFormText() に対応 --> <EditText android:id="@+id/input_form" android:layout_width="184dp" android:layout_height="wrap_content" android:hint="テキストを入力" android:inputType="text" android:text="@={viewModel.formText}" ←ここを追加 app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@id/button" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintVertical_bias="0.3" /> <!-- ViewModelの isButtonEnable に対応 --> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="ボタン" android:enabled="@{viewModel.buttonEnable}" ←ここを追加 app:layout_constraintStart_toEndOf="@id/input_form" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="@id/input_form" /> <!-- ・・・ 省略 ・・・ -->処理の流れ
- View側でフォームの値を変更(入力または削除)したときに
ViewModel
のsetFormText()
が呼ばれる。- このタイミング(
formText
が変更されたタイミング)でnotifyPropertyChanged(BR.buttonEnable)
によりisButtonEnable()
を呼ぶ。isButtonEnable()
でformText
の値があるかどうかによりboolean
を返す- View側
android:enabled="@{viewModel.buttonEnable}"
にboolean
が入ってくることによりボタンの活性非活性が反映されるポイント
@={viewModel.formText}
により双方向データバインディングが実現される。つまりView側でフォームの値を変更(入力または削除)したときにViewModel
のsetFormText()
が呼ばれるようになる。
@=
ではなく@
だと一方向のデータバインディングとなり、フォーム入力内容の変更が検知されずsetFormText()
が呼ばれないため注意。フォーム入力内容をリアルタイム表示させる
ViewModel
以下を追加
setFormText()
内にnotifyPropertyChanged(BR.realTimeText);
getRealTimeText()
ViewModel.javapublic class ViewModel extends BaseObservable { // 代入せずにnullでも良いが今回は "" を初期値とする。 private String formText = ""; // フォーム(EditText)の入力値の getter @Bindable public String getFormText() { return formText; } // フォーム(EditText)の入力値の setter public void setFormText(String formText) { this.formText = formText; // この記述でgetRealTimeText()が呼ばれる notifyPropertyChanged(BR.realTimeText); // この記述でisButtonEnable()が呼ばれる notifyPropertyChanged(BR.buttonEnable); } // フォーム(EditText)下にリアルタイムで表示するテキスト(TextView)の getter @Bindable public String getRealTimeText() { // フォーム入力されているテキストを代入して返す String realTimeText = formText; return realTimeText; } // フォーム(EditText)へのテキスト入力有無で、ボタン活性・非活性を制御するフラグの getter @Bindable public boolean isButtonEnable() { // 入力あり:true 入力なし:false return !TextUtils.isEmpty(formText); } }レイアウト
フォーム入力の内容をリアルタイムで表示したいTextViewに
android:text="@{viewModel.realTimeText}"
を記述activity_main.xml<!-- 一部抜粋して記述 --> <!-- ViewModelのgetRealTimeText()に対応 --> <TextView android:id="@+id/realTimeText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{viewModel.realTimeText}" ←ここを追加 android:textSize="20dp" app:layout_constraintStart_toStartOf="@id/input_form" app:layout_constraintTop_toBottomOf="@id/input_form" android:layout_marginTop="16dp" />処理の流れ
- フォーム入力内容の変更で
setFormText()
内のnotifyPropertyChanged(BR.realTimeText);
によりgetRealTimeText()
が呼ばれる。getRealTimeText()
の返り値がレイアウトのandroid:text="@{viewModel.realTimeText}"
`に取得され表示される。ポイント
notifyPropertyChanged(BR.~)
によりプロパティの変更を通知できる。
getter
に@Bindable
を付与する。そうすることでモジュールパッケージ内のBRクラス
にデータバインディングで使用するリソースIDを持つ監視変数が作成されるため、このような記述が可能になる。詳しくはこちらを参照
BRクラスへ監視変数作成例
以下二つを記述
@Bindable public String getFormText()
@Bindable public boolean isButtonEnable()
BR.javapublic class BR { public static final int formText = 1; public static final int buttonEnable = 2; }ボタンクリックで テキスト表示+フォーム初期化 をさせる
ViewModel
以下を追加
- 変数
clickText
getClickText()
onButtonClick()
ViewModel.javapublic class ViewModel extends BaseObservable { // 代入せずにnullでも良いが今回は "" を初期値とする。 private String formText = ""; // 初期値をセット private String clickText = "ボタンクリックでここに表示"; // フォーム(EditText)の入力値の getter @Bindable public String getFormText() { return formText; } // ボタンクリック時に表示するテキスト(TextView)の getter @Bindable public String getClickText() { return clickText; } // フォーム(EditText)の入力値の setter public void setFormText(String formText) { this.formText = formText; // この記述でgetRealTimeText()が呼ばれる notifyPropertyChanged(BR.realTimeText); // この記述でisButtonEnable()が呼ばれる notifyPropertyChanged(BR.buttonEnable); } // フォーム(EditText)下にリアルタイムで表示するテキスト(TextView)の getter @Bindable public String getRealTimeText() { // フォーム入力されているテキストを代入して返す String realTimeText = formText; return realTimeText; } // フォーム(EditText)へのテキスト入力有無で、ボタン活性・非活性を制御するフラグの getter @Bindable public boolean isButtonEnable() { // 入力あり:true 入力なし:false return !TextUtils.isEmpty(formText); } // ボタンクリックイベント public void onButtonClick() { // clickTextにフォーム入力テキストにセット clickText = formText; // formTextは初期化 formText = ""; // 変更を通知する // この記述でgetClickText()が呼ばれる notifyPropertyChanged(BR.clickText); // この記述でgetFormText()が呼ばれる notifyPropertyChanged(BR.formText); } }レイアウト
以下を追加
- ボタンクリック時表示したいテキストに
android:text="@{viewModel.clickText}"
- ボタンに
android:onClick="@{() -> viewModel.onButtonClick()}"
activity_main.xml<!-- クリック時表示テキストを抜粋 --> <!-- ViewModelのgetClickText()に対応 --> <TextView android:id="@+id/click_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20dp" android:text="@{viewModel.clickText}" ←ここを追加 app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintVertical_bias="0.15" /> <!-- ボタン部分を抜粋 --> <!-- ViewModelのonButtonClick()に対応 --> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="ボタン" android:enabled="@{viewModel.buttonEnable}" android:onClick="@{() -> viewModel.onButtonClick()}" ←ここを追加 app:layout_constraintStart_toEndOf="@id/input_form" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="@id/input_form" />ポイント
ボタンクリックしたときの処理の流れ
onButtonClick()
が呼ばれるclickText
にそのときのフォーム入力内容を代入formText
に""
を代入して初期化notifyPropertyChanged(BR.clickText/formText)
でそれぞれの変数の変更をView側に通知getClickText()
とgetRealTimeText()
が呼ばれ、 View側に変数の値が反映される補足
Viewが描画される?(アクティビティとバインディングされる?)タイミングでも全ての
getter
が呼ばれていた。それによりView側に変数の値が反映される。参考
https://developer.android.com/topic/libraries/data-binding/observability?hl=ja
最後に
わかりやすく書こうと頑張ってかきました。しかし、かえって冗長でわかりにくくなってしまっていたらすみません。
ご指摘やこうした方が良いなどなどありましたら気軽にコメントいただければ嬉しいです!DataBinding やっと慣れてきたかも...
冒頭にも記載しましたが、Kotlin でも同様のサンプルを作成しようと思っております。
- 投稿日:2021-01-22T20:24:30+09:00
【Android / Java】双方向 DataBinding + Clickイベント サンプル
はじめに
実務案件で DataBinding を利用している。
なかなか難しい。そこで双方向 DataBinding を用いたサンプルアプリを作成しながら DataBinding について学んだ。※ 今回は Java ですが、 Kotlin でも同様のものを作成してみようと思っております。その時は改めて投稿します。
サンプルアプリの概要
実装していることとしては
- フォーム入力時
- 入力されたテキストをフォーム下部にリアルタイムで表示
- ボタンアクティブにする(テキストの有無で切り替え)
- ボタンクリック時
- フォーム入力テキストを上部に表示
- フォームおよび下部のテキストを空欄に戻す(初期化する)
開発環境
実装
DataBinding導入
build.gradle
に記述を追加して DataBindingを利用できるようにする。app/build.gradleplugins { id 'com.android.application' } android { compileSdkVersion 30 buildToolsVersion "30.0.3" defaultConfig { applicationId "com.android.bidirectionaldatabindingsample" minSdkVersion 24 targetSdkVersion 30 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 } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'com.google.android.material:material:1.2.1' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' testImplementation 'junit:junit:4.+' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' }レイアウト要素をセット
まずは各要素には DataBinding の記述無しの状態で xml にのレイアウトを作成。
※
<layout></layout>
だけは先に記述。
このようにレイアウトのルート要素に<layout>
でくくったレイアウトファイルがあると、自動的にxmlファイル名に応じたBindingクラスが作られる。
今回: activity_main.xml => ActivityMainBindingactivity_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" > <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/click_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintVertical_bias="0.15" /> <EditText android:id="@+id/input_form" android:layout_width="184dp" android:layout_height="wrap_content" android:hint="テキストを入力" android:inputType="text" android:textSize="20dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@id/button" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintVertical_bias="0.3" /> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="ボタン" app:layout_constraintStart_toEndOf="@id/input_form" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="@id/input_form" /> <TextView android:id="@+id/realTimeText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20dp" app:layout_constraintStart_toStartOf="@id/input_form" app:layout_constraintTop_toBottomOf="@id/input_form" android:layout_marginTop="16dp" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout>DataBinding のロジック定義をするViewModelを作成する
ViewModel.java// BaseObservableクラスを継承 public class ViewModel extends BaseObservable { // ここにロジックを書いていく }MainActivityでViewModelをバインドする(紐づける)
MainActivity.javapublic class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); setTitle("双方向データバインディングサンプル"); // Bindingインスタンスを生成 ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main); // ViewModelオブジェクトとバインドする(紐付ける) ViewModel viewModel = new ViewModel(); binding.setViewModel(viewModel); } }レイアウトファイルでViewModelオブジェクトを利用できるようにする
レイアウトファイルに記述を追加
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" > <data> <!-- viewModelという名前でViewModelオブジェクトを登録 --> <variable name="viewModel" type="com.android.bidirectionaldatabindingsample.ViewModel" /> </data> <!-- ・・・ 省略 ・・・ -->準備が整ったのでこれから機能を実装していく。
フォーム入力でボタンアクティブにする
ViewModel
以下を記述
formText
getFormText()
setFormtext()
isButtonEnable()
ViewModel.javapublic class ViewModel extends BaseObservable { // 代入せずにnullでも良いが今回は "" を初期値とする。 private String formText = ""; // フォーム(EditText)の入力値の getter @Bindable public String getFormText() { return formText; } // フォーム(EditText)の入力値の setter public void setFormText(String formText) { this.formText = formText; // この記述でisButtonEnable()が呼ばれる notifyPropertyChanged(BR.buttonEnable); } // フォーム(EditText)へのテキスト入力有無で、ボタン活性・非活性を制御するフラグの getter @Bindable public boolean isButtonEnable() { // 入力あり:true 入力なし:false return !TextUtils.isEmpty(formText); } }レイアウト
以下を記述
- フォーム要素に
android:text="@={viewModel.formText}"
- ボタン要素に
android:enabled="@{viewModel.buttonEnable}"
activity_main.xml<!-- ・・・ 省略 ・・・ --> <!-- 「@={}」で双方向のバインディング --> <!-- ViewModelの getFormText() と setFormText() に対応 --> <EditText android:id="@+id/input_form" android:layout_width="184dp" android:layout_height="wrap_content" android:hint="テキストを入力" android:inputType="text" android:text="@={viewModel.formText}" ←ここを追加 app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@id/button" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintVertical_bias="0.3" /> <!-- ViewModelの isButtonEnable に対応 --> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="ボタン" android:enabled="@{viewModel.buttonEnable}" ←ここを追加 app:layout_constraintStart_toEndOf="@id/input_form" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="@id/input_form" /> <!-- ・・・ 省略 ・・・ -->処理の流れ
- View側でフォームの値を変更(入力または削除)したときに
ViewModel
のsetFormText()
が呼ばれる。- このタイミング(
formText
が変更されたタイミング)でnotifyPropertyChanged(BR.buttonEnable)
によりisButtonEnable()
を呼ぶ。isButtonEnable()
でformText
の値があるかどうかによりboolean
を返す- View側
android:enabled="@{viewModel.buttonEnable}"
にboolean
が入ってくることによりボタンの活性非活性が反映されるポイント
@={viewModel.formText}
により双方向データバインディングが実現される。つまりView側でフォームの値を変更(入力または削除)したときにViewModel
のsetFormText()
が呼ばれるようになる。
@=
ではなく@
だと一方向のデータバインディングとなり、フォーム入力内容の変更が検知されずsetFormText()
が呼ばれないため注意。フォーム入力内容をリアルタイム表示させる
ViewModel
以下を追加
setFormText()
内にnotifyPropertyChanged(BR.realTimeText);
getRealTimeText()
ViewModel.javapublic class ViewModel extends BaseObservable { // 代入せずにnullでも良いが今回は "" を初期値とする。 private String formText = ""; // フォーム(EditText)の入力値の getter @Bindable public String getFormText() { return formText; } // フォーム(EditText)の入力値の setter public void setFormText(String formText) { this.formText = formText; // この記述でgetRealTimeText()が呼ばれる notifyPropertyChanged(BR.realTimeText); // この記述でisButtonEnable()が呼ばれる notifyPropertyChanged(BR.buttonEnable); } // フォーム(EditText)下にリアルタイムで表示するテキスト(TextView)の getter @Bindable public String getRealTimeText() { // フォーム入力されているテキストを代入して返す String realTimeText = formText; return realTimeText; } // フォーム(EditText)へのテキスト入力有無で、ボタン活性・非活性を制御するフラグの getter @Bindable public boolean isButtonEnable() { // 入力あり:true 入力なし:false return !TextUtils.isEmpty(formText); } }レイアウト
フォーム入力の内容をリアルタイムで表示したいTextViewに
android:text="@{viewModel.realTimeText}"
を記述activity_main.xml<!-- 一部抜粋して記述 --> <!-- ViewModelのgetRealTimeText()に対応 --> <TextView android:id="@+id/realTimeText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{viewModel.realTimeText}" ←ここを追加 android:textSize="20dp" app:layout_constraintStart_toStartOf="@id/input_form" app:layout_constraintTop_toBottomOf="@id/input_form" android:layout_marginTop="16dp" />処理の流れ
- フォーム入力内容の変更で
setFormText()
内のnotifyPropertyChanged(BR.realTimeText);
によりgetRealTimeText()
が呼ばれる。getRealTimeText()
の返り値がレイアウトのandroid:text="@{viewModel.realTimeText}"
`に取得され表示される。ポイント
notifyPropertyChanged(BR.~)
によりプロパティの変更を通知できる。
getter
に@Bindable
を付与する。そうすることでモジュールパッケージ内のBRクラス
にデータバインディングで使用するリソースIDを持つ監視変数が作成されるため、このような記述が可能になる。詳しくはこちらを参照
BRクラスへ監視変数作成例
以下二つを記述
@Bindable public String getFormText()
@Bindable public boolean isButtonEnable()
BR.javapublic class BR { public static final int formText = 1; public static final int buttonEnable = 2; }ボタンクリックで テキスト表示+フォーム初期化 をさせる
ViewModel
以下を追加
- 変数
clickText
getClickText()
onButtonClick()
ViewModel.javapublic class ViewModel extends BaseObservable { // 代入せずにnullでも良いが今回は "" を初期値とする。 private String formText = ""; // 初期値をセット private String clickText = "ボタンクリックでここに表示"; // フォーム(EditText)の入力値の getter @Bindable public String getFormText() { return formText; } // ボタンクリック時に表示するテキスト(TextView)の getter @Bindable public String getClickText() { return clickText; } // フォーム(EditText)の入力値の setter public void setFormText(String formText) { this.formText = formText; // この記述でgetRealTimeText()が呼ばれる notifyPropertyChanged(BR.realTimeText); // この記述でisButtonEnable()が呼ばれる notifyPropertyChanged(BR.buttonEnable); } // フォーム(EditText)下にリアルタイムで表示するテキスト(TextView)の getter @Bindable public String getRealTimeText() { // フォーム入力されているテキストを代入して返す String realTimeText = formText; return realTimeText; } // フォーム(EditText)へのテキスト入力有無で、ボタン活性・非活性を制御するフラグの getter @Bindable public boolean isButtonEnable() { // 入力あり:true 入力なし:false return !TextUtils.isEmpty(formText); } // ボタンクリックイベント public void onButtonClick() { // clickTextにフォーム入力テキストにセット clickText = formText; // formTextは初期化 formText = ""; // 変更を通知する // この記述でgetClickText()が呼ばれる notifyPropertyChanged(BR.clickText); // この記述でgetFormText()が呼ばれる notifyPropertyChanged(BR.formText); } }レイアウト
以下を追加
- ボタンクリック時表示したいテキストに
android:text="@{viewModel.clickText}"
- ボタンに
android:onClick="@{() -> viewModel.onButtonClick()}"
activity_main.xml<!-- クリック時表示テキストを抜粋 --> <!-- ViewModelのgetClickText()に対応 --> <TextView android:id="@+id/click_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20dp" android:text="@{viewModel.clickText}" ←ここを追加 app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintVertical_bias="0.15" /> <!-- ボタン部分を抜粋 --> <!-- ViewModelのonButtonClick()に対応 --> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="ボタン" android:enabled="@{viewModel.buttonEnable}" android:onClick="@{() -> viewModel.onButtonClick()}" ←ここを追加 app:layout_constraintStart_toEndOf="@id/input_form" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="@id/input_form" />ポイント
ボタンクリックしたときの処理の流れ
onButtonClick()
が呼ばれるclickText
にそのときのフォーム入力内容を代入formText
に""
を代入して初期化notifyPropertyChanged(BR.clickText/formText)
でそれぞれの変数の変更をView側に通知getClickText()
とgetRealTimeText()
が呼ばれ、 View側に変数の値が反映される補足
Viewが描画される?(アクティビティとバインディングされる?)タイミングでも全ての
getter
が呼ばれていた。それによりView側に変数の値が反映される。参考
https://developer.android.com/topic/libraries/data-binding/observability?hl=ja
最後に
わかりやすく書こうと頑張ってかきました。しかし、かえって冗長でわかりにくくなってしまっていたらすみません。
ご指摘やこうした方が良いなどなどありましたら気軽にコメントいただければ嬉しいです!DataBinding やっと慣れてきたかも...
冒頭にも記載しましたが、Kotlin でも同様のサンプルを作成しようと思っております。
- 投稿日:2021-01-22T20:24:30+09:00
【Android / Java】双方向 DataBinding + Clickイベント / サンプルアプリ実装
はじめに
実務案件で DataBinding を利用している。
なかなか難しい。そこで双方向 DataBinding を用いたサンプルアプリを作成しながら DataBinding について学んだ。※ 今回は Java ですが、
Kotlin でも同様のものを作成してみようと思っております。その時は改めて投稿します。
Kotlin バージョンも作成して記事にしましたのでご覧いただければ幸いです!!↓↓
【Android / Kotlin】双方向 DataBinding + Clickイベント / サンプルアプリ実装双方向データバインディングとは(一方向との違い)
- 一方行データバインディング
- ViewModel などの中でデータを変更 → View に反映
- 双方向データバインディング
- ViewModel などの中でデータを変更 → View に反映
- ユーザが View でデータを変更 → ViewModel にデータの変更が反映(通知)
一方向が ViewModel などのロジックファイルから View 側への通知だけであるのに対し、
双方向ではユーザがアプリのフォームなどに値を入力したときなどに、ロジックファイルの方にも値の入力(変更)が通知される。と言うものである。なお一方向データバインディングについてはこちらで記事にしております。
※ ここで説明している ViewModel とはデータバインディングのロジックを定義してあるファイルのことを指しています。
サンプルアプリの概要
実装していることとしては
- フォーム入力時
- 入力されたテキストをフォーム下部にリアルタイムで表示
- ボタンアクティブにする(テキストの有無で切り替え)
- ボタンクリック時
- フォーム入力テキストを上部に表示
- フォームおよび下部のテキストを空欄に戻す(初期化する)
開発環境
- Android Studio: 4.1.1
- Build #AI-201.8743.12.41.6953283, built on November 5, 2020
- Runtime version: 1.8.0_242-release-1644-b3-6915495 x86_64
- VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
- macOS: 10.15.7
実装
DataBinding導入
build.gradle
に記述を追加して DataBindingを利用できるようにする。app/build.gradleplugins { id 'com.android.application' } android { compileSdkVersion 30 buildToolsVersion "30.0.3" defaultConfig { applicationId "com.android.bidirectionaldatabindingsample" minSdkVersion 24 targetSdkVersion 30 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 } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'com.google.android.material:material:1.2.1' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' testImplementation 'junit:junit:4.+' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' }レイアウト要素をセット
まずは各要素には DataBinding の記述無しの状態で xml にのレイアウトを作成。
※
<layout></layout>
だけは先に記述。
このようにレイアウトのルート要素に<layout>
でくくったレイアウトファイルがあると、自動的にxmlファイル名に応じたBindingクラスが作られる。
今回: activity_main.xml => ActivityMainBindingactivity_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" > <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/click_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintVertical_bias="0.15" /> <EditText android:id="@+id/input_form" android:layout_width="184dp" android:layout_height="wrap_content" android:hint="テキストを入力" android:inputType="text" android:textSize="20dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@id/button" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintVertical_bias="0.3" /> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="ボタン" app:layout_constraintStart_toEndOf="@id/input_form" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="@id/input_form" /> <TextView android:id="@+id/realTimeText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20dp" app:layout_constraintStart_toStartOf="@id/input_form" app:layout_constraintTop_toBottomOf="@id/input_form" android:layout_marginTop="16dp" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout>DataBinding のロジック定義をするViewModelを作成する
ViewModel.java// BaseObservableクラスを継承 public class ViewModel extends BaseObservable { // ここにロジックを書いていく }MainActivityでViewModelをバインドする(紐づける)
MainActivity.javapublic class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); setTitle("双方向データバインディングサンプル"); // Bindingインスタンスを生成 ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main); // ViewModelオブジェクトとバインドする(紐付ける) ViewModel viewModel = new ViewModel(); binding.setViewModel(viewModel); } }レイアウトファイルでViewModelオブジェクトを利用できるようにする
レイアウトファイルに記述を追加
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" > <data> <!-- viewModelという名前でViewModelオブジェクトを登録 --> <variable name="viewModel" type="com.android.bidirectionaldatabindingsample.ViewModel" /> </data> <!-- ・・・ 省略 ・・・ -->準備が整ったのでこれから機能を実装していく。
フォーム入力でボタンアクティブにする
ViewModel
以下を記述
formText
getFormText()
setFormtext()
isButtonEnable()
ViewModel.javapublic class ViewModel extends BaseObservable { // 代入せずにnullでも良いが今回は "" を初期値とする。 private String formText = ""; // フォーム(EditText)の入力値の getter @Bindable public String getFormText() { return formText; } // フォーム(EditText)の入力値の setter public void setFormText(String formText) { this.formText = formText; // この記述でisButtonEnable()が呼ばれる notifyPropertyChanged(BR.buttonEnable); } // フォーム(EditText)へのテキスト入力有無で、ボタン活性・非活性を制御するフラグの getter @Bindable public boolean isButtonEnable() { // 入力あり:true 入力なし:false return !TextUtils.isEmpty(formText); } }レイアウト
以下を記述
- フォーム要素に
android:text="@={viewModel.formText}"
- ボタン要素に
android:enabled="@{viewModel.buttonEnable}"
activity_main.xml<!-- ・・・ 省略 ・・・ --> <!-- 「@={}」で双方向のバインディング --> <!-- ViewModelの getFormText() と setFormText() に対応 --> <EditText android:id="@+id/input_form" android:layout_width="184dp" android:layout_height="wrap_content" android:hint="テキストを入力" android:inputType="text" android:text="@={viewModel.formText}" ←ここを追加 app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@id/button" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintVertical_bias="0.3" /> <!-- ViewModelの isButtonEnable に対応 --> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="ボタン" android:enabled="@{viewModel.buttonEnable}" ←ここを追加 app:layout_constraintStart_toEndOf="@id/input_form" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="@id/input_form" /> <!-- ・・・ 省略 ・・・ -->処理の流れ
- View側でフォームの値を変更(入力または削除)したときに
ViewModel
のsetFormText()
が呼ばれる。- このタイミング(
formText
が変更されたタイミング)でnotifyPropertyChanged(BR.buttonEnable)
によりisButtonEnable()
を呼ぶ。isButtonEnable()
でformText
の値があるかどうかによりboolean
を返す- View側
android:enabled="@{viewModel.buttonEnable}"
にboolean
が入ってくることによりボタンの活性非活性が反映されるポイント
@={viewModel.formText}
により双方向データバインディングが実現される。つまりView側でフォームの値を変更(入力または削除)したときにViewModel
のsetFormText()
が呼ばれるようになる。
@=
ではなく@
だと一方向のデータバインディングとなり、フォーム入力内容の変更が検知されずsetFormText()
が呼ばれないため注意。フォーム入力内容をリアルタイム表示させる
ViewModel
以下を追加
setFormText()
内にnotifyPropertyChanged(BR.realTimeText);
getRealTimeText()
ViewModel.javapublic class ViewModel extends BaseObservable { // 代入せずにnullでも良いが今回は "" を初期値とする。 private String formText = ""; // フォーム(EditText)の入力値の getter @Bindable public String getFormText() { return formText; } // フォーム(EditText)の入力値の setter public void setFormText(String formText) { this.formText = formText; // この記述でgetRealTimeText()が呼ばれる notifyPropertyChanged(BR.realTimeText); // この記述でisButtonEnable()が呼ばれる notifyPropertyChanged(BR.buttonEnable); } // フォーム(EditText)下にリアルタイムで表示するテキスト(TextView)の getter @Bindable public String getRealTimeText() { // フォーム入力されているテキストを代入して返す String realTimeText = formText; return realTimeText; } // フォーム(EditText)へのテキスト入力有無で、ボタン活性・非活性を制御するフラグの getter @Bindable public boolean isButtonEnable() { // 入力あり:true 入力なし:false return !TextUtils.isEmpty(formText); } }レイアウト
フォーム入力の内容をリアルタイムで表示したいTextViewに
android:text="@{viewModel.realTimeText}"
を記述activity_main.xml<!-- 一部抜粋して記述 --> <!-- ViewModelのgetRealTimeText()に対応 --> <TextView android:id="@+id/realTimeText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{viewModel.realTimeText}" ←ここを追加 android:textSize="20dp" app:layout_constraintStart_toStartOf="@id/input_form" app:layout_constraintTop_toBottomOf="@id/input_form" android:layout_marginTop="16dp" />処理の流れ
- フォーム入力内容の変更で
setFormText()
内のnotifyPropertyChanged(BR.realTimeText);
によりgetRealTimeText()
が呼ばれる。getRealTimeText()
の返り値がレイアウトのandroid:text="@{viewModel.realTimeText}"
`に取得され表示される。ポイント
notifyPropertyChanged(BR.~)
によりプロパティの変更を通知できる。
getter
に@Bindable
を付与する。そうすることでモジュールパッケージ内のBRクラス
にデータバインディングで使用するリソースIDを持つ監視変数が作成されるため、このような記述が可能になる。詳しくはこちらを参照
BRクラスへ監視変数作成例
以下二つを記述
@Bindable public String getFormText()
@Bindable public boolean isButtonEnable()
BR.javapublic class BR { public static final int formText = 1; public static final int buttonEnable = 2; }ボタンクリックで テキスト表示+フォーム初期化 をさせる
ViewModel
以下を追加
- 変数
clickText
getClickText()
onButtonClick()
ViewModel.javapublic class ViewModel extends BaseObservable { // 代入せずにnullでも良いが今回は "" を初期値とする。 private String formText = ""; // 初期値をセット private String clickText = "ボタンクリックでここに表示"; // フォーム(EditText)の入力値の getter @Bindable public String getFormText() { return formText; } // ボタンクリック時に表示するテキスト(TextView)の getter @Bindable public String getClickText() { return clickText; } // フォーム(EditText)の入力値の setter public void setFormText(String formText) { this.formText = formText; // この記述でgetRealTimeText()が呼ばれる notifyPropertyChanged(BR.realTimeText); // この記述でisButtonEnable()が呼ばれる notifyPropertyChanged(BR.buttonEnable); } // フォーム(EditText)下にリアルタイムで表示するテキスト(TextView)の getter @Bindable public String getRealTimeText() { // フォーム入力されているテキストを代入して返す String realTimeText = formText; return realTimeText; } // フォーム(EditText)へのテキスト入力有無で、ボタン活性・非活性を制御するフラグの getter @Bindable public boolean isButtonEnable() { // 入力あり:true 入力なし:false return !TextUtils.isEmpty(formText); } // ボタンクリックイベント public void onButtonClick() { // clickTextにフォーム入力テキストにセット clickText = formText; // formTextは初期化 formText = ""; // 変更を通知する // この記述でgetClickText()が呼ばれる notifyPropertyChanged(BR.clickText); // この記述でgetFormText()が呼ばれる notifyPropertyChanged(BR.formText); } }レイアウト
以下を追加
- ボタンクリック時表示したいテキストに
android:text="@{viewModel.clickText}"
- ボタンに
android:onClick="@{() -> viewModel.onButtonClick()}"
activity_main.xml<!-- クリック時表示テキストを抜粋 --> <!-- ViewModelのgetClickText()に対応 --> <TextView android:id="@+id/click_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20dp" android:text="@{viewModel.clickText}" ←ここを追加 app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintVertical_bias="0.15" /> <!-- ボタン部分を抜粋 --> <!-- ViewModelのonButtonClick()に対応 --> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="ボタン" android:enabled="@{viewModel.buttonEnable}" android:onClick="@{() -> viewModel.onButtonClick()}" ←ここを追加 app:layout_constraintStart_toEndOf="@id/input_form" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="@id/input_form" />ポイント
ボタンクリックしたときの処理の流れ
onButtonClick()
が呼ばれるclickText
にそのときのフォーム入力内容を代入formText
に""
を代入して初期化notifyPropertyChanged(BR.clickText/formText)
でそれぞれの変数の変更をView側に通知getClickText()
とgetRealTimeText()
が呼ばれ、 View側に変数の値が反映される補足
Viewが描画される?(アクティビティとバインディングされる?)タイミングでも全ての
getter
が呼ばれていた。それによりView側に変数の値が反映される。レイアウトファイル全体
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" > <!-- viewModelという名前でViewModelオブジェクトを登録 --> <data> <variable name="viewModel" type="com.android.bidirectionaldatabindingkotlin.ViewModel" /> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/click_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20dp" android:text="@{viewModel.clickText}" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintVertical_bias="0.15" /> <EditText android:id="@+id/input_form" android:layout_width="184dp" android:layout_height="wrap_content" android:hint="テキストを入力" android:inputType="text" android:textSize="20dp" android:text="@={viewModel.formText}" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@id/button" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintVertical_bias="0.3" /> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="ボタン" android:enabled="@{viewModel.buttonEnable}" android:onClick="@{() -> viewModel.onButtonClick()}" app:layout_constraintStart_toEndOf="@id/input_form" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="@id/input_form" /> <TextView android:id="@+id/realTimeText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20dp" android:text="@{viewModel.realTimeText}" app:layout_constraintStart_toStartOf="@id/input_form" app:layout_constraintTop_toBottomOf="@id/input_form" android:layout_marginTop="16dp" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout>参考
https://developer.android.com/topic/libraries/data-binding/observability?hl=ja
最後に
わかりやすく書こうと頑張ってかきました。しかし、かえって冗長でわかりにくくなってしまっていたらすみません。
ご指摘やこうした方が良いなどなどありましたら気軽にコメントいただければ嬉しいです!DataBinding やっと慣れてきたかも...
冒頭にも記載しましたが、
Kotlin でも同様のサンプルを作成しようと思っております。
Kotlin バージョンも作成して記事にしました!!よろしければ↓↓
【Android / Kotlin】双方向 DataBinding + Clickイベント / サンプルアプリ実装
- 投稿日:2021-01-22T19:11:22+09:00
Java dependency シリアル化性能比較
Java シリアルツール性能比較
本ページでは主にjavaのシリアルツールの性能比較を記載します。
コードと結果の部分があります。使用されたのdependency
- Java原生方法
- XStream方式
- FastJson方式
- Hessian方式
シリアルされたデータ
@Data public class User implements Serializable { private static final long serialVersionUID = -2564324657866843L; private String name; private int age; private void writeObject(java.io.ObjectOutputStream s) throws IOException { s.defaultWriteObject(); s.writeObject(name); } private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); name=(String)s.readObject(); } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } }Userのデータをシリアル化されたデータのレングスに基づいて、ツール間の効率を検証します。
効率検証
1・ まずJava原生登場
ByteArrayOutputStream byteArrayOutputStream= new ByteArrayOutputStream(); try { ObjectOutputStream outputStream= new ObjectOutputStream(byteArrayOutputStream); outputStream.writeObject(obj); return byteArrayOutputStream.toByteArray(); } catch (IOException e) { e.printStackTrace(); } return new byte[0];データのレングスはどのぐらいですか?
byte.length:92
2・ 次はXStreamですXMLにベースのツールです
XStream xStream=new XStream(new DomDriver()); @Override public <T> byte[] serialize(T obj) { return xStream.toXML(obj).getBytes(); }データのレングスはどのぐらいですか?
byte.length:221 やはりXMLですね~
3・ そしてFastJson登場、中国企業Alibaba 開発されたのツールです。
return JSON.toJSONString(obj).getBytes();書き方は安いですか?
でデータのレングスはどのぐらいですか?
byte.length:23 すごいのシリアルですね
4・ 最後にはHessianです
ByteArrayOutputStream outputStream=new ByteArrayOutputStream(); HessianOutput hessianOutput=new HessianOutput(outputStream); try { hessianOutput.writeObject(obj); return outputStream.toByteArray(); } catch (IOException e) { e.printStackTrace(); }結果は以下です↓
byte.length:50
まとめ
比較してみると,fastjsonは最も効率的なツールです。でも他のツールが悪いわけではない。fastjsonは複数言語に対応できないので。皆さんは項目の内容に応じてシリアルツール使うしよう!
- 投稿日:2021-01-22T19:11:22+09:00
Java シリアルツール性能比較
Java シリアルツール性能比較
本ページでは主にjavaのシリアルツールの性能比較を記載します。
コードと結果の部分があります。使用されたのdependency
- Java原生方法
- XStream方式
- FastJson方式
- Hessian方式
シリアルされたデータ
@Data public class User implements Serializable { private static final long serialVersionUID = -2564324657866843L; private String name; private int age; private void writeObject(java.io.ObjectOutputStream s) throws IOException { s.defaultWriteObject(); s.writeObject(name); } private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); name=(String)s.readObject(); } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + '}'; } }Userのデータをシリアル化されたデータのレングスに基づいて、ツール間の効率を検証します。
効率検証
1・ まずJava原生登場
ByteArrayOutputStream byteArrayOutputStream= new ByteArrayOutputStream(); try { ObjectOutputStream outputStream= new ObjectOutputStream(byteArrayOutputStream); outputStream.writeObject(obj); return byteArrayOutputStream.toByteArray(); } catch (IOException e) { e.printStackTrace(); } return new byte[0];データのレングスはどのぐらいですか?
byte.length:92
2・ 次はXStreamですXMLにベースのツールです
XStream xStream=new XStream(new DomDriver()); @Override public <T> byte[] serialize(T obj) { return xStream.toXML(obj).getBytes(); }データのレングスはどのぐらいですか?
byte.length:221 やはりXMLですね~
3・ そしてFastJson登場、中国企業Alibaba 開発されたのツールです。
return JSON.toJSONString(obj).getBytes();書き方は安いですか?
でデータのレングスはどのぐらいですか?
byte.length:23 すごいのシリアルですね
4・ 最後にはHessianです
ByteArrayOutputStream outputStream=new ByteArrayOutputStream(); HessianOutput hessianOutput=new HessianOutput(outputStream); try { hessianOutput.writeObject(obj); return outputStream.toByteArray(); } catch (IOException e) { e.printStackTrace(); }結果は以下です↓
byte.length:50
まとめ
比較してみると,fastjsonは最も効率的なツールです。でも他のツールが悪いわけではない。fastjsonは複数言語に対応できないので。皆さんは項目の内容に応じてシリアルツール使うしよう!
- 投稿日:2021-01-22T16:25:16+09:00
Java リフレクションでメソッド実行
Javaでリフレクションを実行した際の備忘録
最後尾にサンプルソースを記載している◆文字列をクラスオブジェクトに変換する
// 文字列をクラスオブジェクトに変換する String strClass = "imamu.reflect.Called"; Class<?> c = Class.forName(strClass);◆クラス名とメソッドの取得方法
// クラス名の取得 System.out.println("Class name: " + c.getName()); // メソッドの取得 for (Method m : c.getMethods()) { boolean isFirstParam = true; System.out.print(" " + m.getName() + "("); for (Class<?> p : m.getParameterTypes()) { if (!isFirstParam) { System.out.print(","); } System.out.print(p.getName()); isFirstParam = false; } System.out.println(")"); }◆クラスの実行(staticメソッド、引数が配列)
※ static void main(string[] args)を実行する// 実行するメソッドと引数を定義する String strMethod = "main"; Method m = c.getMethod(strMethod,String[].class); //引数をオブジェクト化する必要がある String[] strParam = {}; Object[] objParam = new Object[] {strParam}; // 実行 m.invoke(c,objParam);◆クラスのインスタンス化と実行
Class.newInstance()は非推奨なので要注意
Class.getDeclaredConstructor().newInstance()とすること//インスタンス化 Object obj = c.getDeclaredConstructor().newInstance(); // メソッドの定義と実行 strMethod = "execTest"; Method m2 = c.getMethod(strMethod); m2.invoke(obj);◆プライベートなstaticメソッドの実行、戻り値あり
// メソッドの定義と実行 strMethod = "privateTest"; Method m3 = c.getMethod(strMethod,String.class,Integer.class); // private m3.setAccessible(true); Object rtn = m3.invoke(c,"test",500); System.out.println(rtn.toString());★呼び出される側のクラス
/** * 呼ばれる側のクラス */ public class Called { public static void main(String[] args) { // TODO 自動生成されたメソッド・スタブ System.out.println("mainメソッドが実行されました"); } public void execTest() { System.out.println("要インスタンス化ーexecTestが実行されました"); } public static Integer privateTest(String str,Integer i) { System.out.println("要インスタンス化ーprivateTestが実行されました"); return 200; } }★呼び出す側のクラス
import java.lang.reflect.Method;
/** * 呼ぶ側のクラス */ public class Call { /** * * @param args * @throws Exception */ public static void main(String[] args) throws Exception{ // 文字列をクラスオブジェクトに変換する String strClass = "imamu.reflect.Called"; Class<?> c = Class.forName(strClass); // クラス名とメソッドの取得 getClassNameAndMethod(c); /*クラスの実行(staticメソッド、引数が配列) ※ static void main(string[] args)を実行する */ // 実行するメソッドと引数を定義する String strMethod = "main"; Method m1 = c.getMethod(strMethod,String[].class); //引数をオブジェクト化する必要がある String[] strParam = {}; Object[] objParam = new Object[] {strParam}; // 実行 m1.invoke(c,objParam); /* クラスのインスタンス化と実行 */ //インスタンス化 Object obj = c.getDeclaredConstructor().newInstance(); // メソッドの定義と実行 strMethod = "execTest"; Method m2 = c.getMethod(strMethod); m2.invoke(obj); /* プライベートなstaticメソッドの実行、戻り値あり */ // メソッドの定義と実行 strMethod = "privateTest"; Method m3 = c.getMethod(strMethod,String.class,Integer.class); // private m3.setAccessible(true); Object rtn = m3.invoke(c,"test",500); System.out.println(rtn.toString()); System.out.println("おしまい"); } /** * * @param c */ private static void getClassNameAndMethod(Class<?> c ) { // クラス名の取得 System.out.println("Class name: " + c.getName()); // メソッドの取得 for (Method m : c.getMethods()) { boolean isFirstParam = true; System.out.print(" " + m.getName() + "("); for (Class<?> p : m.getParameterTypes()) { if (!isFirstParam) { System.out.print(","); } System.out.print(p.getName()); isFirstParam = false; } System.out.println(")"); } }}