Android実機にデバッグ版がインストールできない JsonSyntaxException: com.google.gson.stream.MalformedJsonException: Invalid escape sequence at line 1

今日は表題のエラーで5時間ぐらいを費やしました…。

王大人「死亡確認!!」

さて、不思議なことが起こりまして、とあるAndroidアプリを、Release版はインストールできるんですが、Debug版が実機でインストールできない。

上記のエラーが出るんですよね。

最初は、google-services.json関係のエラーかと思って、その辺をFirebaseからダウンロードしたりしてましたが、直りませんでした。

結局、ほかのアプリで実績のある、gradleのバージョンとgoogle-servicesのバージョンの組み合わせで試したところ、無事に解決…。

buildscript {
repositories {
jcenter()
google()
}
dependencies {

classpath 'com.android.tools.build:gradle:3.2.1' //これ
classpath 'com.google.gms:google-services:3.2.0' // これ

}
}
 


Vue.jsを使って、facebookの通知エリアみたいなものを作るサンプルコード

「facebookの通知エリアみたいなもの」と言われても漠然としていると思いますが表示でいうと次のようなものです。

文章で要件を書くと次の通りです。

  • アイコンの右肩or左肩に未読件数のバッジが付いている
  • アイコンにマウスオーバーで下にプルダウンで表示される
  • メッセージ内容は動的に変更できる
  • 一件一件選んで消せる
  • 消したものは、既読フラグをつけて、もう未読エリアに表示しない
  • 重要なメッセージは色を変更するなどでわかりやすくする

私の javascript に関する能力としては、Web関係の仕事を5.6年前までは結構一生懸命やっていたけれども、javascriptは結局必要に迫られたときにjQueryとか jQuery UIとかでなんとかしていて、そんなに深くはやっていない、という感じのスキルです。

今回は、ちょっと急ぎで上記のものを作らないといけなくって、

「はは!この秀吉が、一夜で城を建ててみまする!」

という感じで、サクッとやる感じでやりました。結局4日間ぐらいはかかりましたので、4日城ですね。

使っているのは

  • Vue.js
  • Vuetify.js(バッジのデザインをこれにしたかった)
  • Semantic UI (メッセージのデザインをこれにしたかった)
  • axios(閉じるボタンをクリックした時に何か実行するため)

びみょいところもありますが、載せちゃいます。

<!DOCTYPE html>
<html>
<head>
<link href="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.min.css"
	rel="stylesheet">
<link rel="stylesheet"
	href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css">

</head>
<body>
	<br>
	<div id="app">
		<div class="ui button"
			style="background-color: #ffffff; margin-left: 100px;">
			<!-- メールのアイコン -->
			<v-app style="height:50px; background-color:#ffffff;"> 
				<v-layout /> 
					<a>
						<v-badge color="red"  left> 
							<template v-slot:badge> 
								<span v-if="messages > 0">{{ messages }}</span>
							</template> 
							<bell v-on:showmenu="showMenuBelowBell()"
							v-on:hidemenu="hideMenuBelowBell()">
							</bell>
						</v-badge>					
					</a>
				</v-layout>
			</v-app>

			<!-- 通知 -->
			<notification v-for="post in posts" v-bind:key="post.id"
				v-bind:title="post.title" v-bind:content="post.content"
				v-bind:type="post.type" v-if="show_messages"></notification>
		</div>
	</div>

	<!-- 通知部分のテンプレート -->
	<script type="text/x-template" id="notification-component">
	  <div class="ui message" :class="type" v-if="!hidden" style="width:300px; text-align:left">	  
		<i class="close icon" @click="hide"></i>
		<div class="header">{{ title }}</div>
		<slot></slot>
		<div v-html="content"></div>
   	  </div>
	</script>

	<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
	<script src="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.js"></script>
	<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

	<script>

	  Vue.component('bell', ({
			  template: '<span><i class="bell icon" v-on:mouseover="mouseover" ></i></span>',
	     	  methods: {
	  		    mouseover: function(){
	  		      console.log("マウスオーバーだよ");
		  		  this.$emit('showmenu') 
	  		    },
	
	  		    mouseleave:function(){
	  			  console.log("マウスリーブだよ");
	  			  this.$emit('hidemenu')
	  			},
	  		  }
		  	      
		}))	
			
  
	  Vue.component('notification', {
		  props: {
			  		title:{}, 
			  	  	content:{},
			        type: {
			            type: String,
			            default: 'info'
			         },
			        header: {
			       }
		  },
	      data() {
		        return {
		          hidden: false,
		   		 
		        }
		  },
	      methods: {
	          hide() {
	            this.hidden = true
	            axios.get(`https://api.github.com/users/AkikoGoto`)
	            .then(response => {
	              console.log(response.data)
	            })
	          },
	
	      },
		  template: '#notification-component'
		})	
		
		
		
	    
	  new Vue({
		  el: '#app',
		  vuetify: new Vuetify(),
		  data () {
		    return {
		     // show: false,
	
	 	      posts: [
	  		      { id: 1, title: 'せやかて' , content:'工藤', type:'info'},
	  		      { id: 2, title: 'なんだと' , content:'服部!本当なのか?', type:'error'},
	  		      { id: 3, title: 'お知らせ' , content:'ついに黒の組織のボスが判明'}
	  		    ],
	
	  		 messages: 3,
	  		 show_messages:false,
	
		    }
		  },
	
		  methods: {
	
			  showMenuBelowBell: function () {
			      this.show_messages = true
	
			    },
	
			  hideMenuBelowBell: function () {
				  this.show_messages = false
	
			    }		    		    
			    
			  }
	
		})
	
  </script>
</body>
</html>

一応、これを張り付けてhtmlファイルとしてブラウザで開けば、一番最初の画像のような挙動になるはずです。

注意点は次の通り。

  • データはVueを生成しているところ(new Vueの箇所)のpostsというところに入っています。JSONだとかなんらかの形で、ここが動的に変わるようにしてください。
  • バッジの件数は、その下のmessagesというオブジェクトになります。すみませんが、上記のpostsを自動的にカウントして入れるようになっていませんので、ここも別途やってくださいませ。
  • 閉じる×ボタンをクリックした時の挙動は、私のGithubにつながって応答が返ってきてconsoleに出力されるようになってます…。もちろん、ここも皆様がカスタマイズされると思いますので、ただのサンプルです。
  • プルダウンメニューが表示されるところはやってありますが、全体的に隠す処理はしていません。そちらもご自由に…。

Java 桁の大きいLongがマイナスの数字になってしまう

しょぼい失敗ではありますが。(´・ω・`)

例えば、31日間のミリセカンドを取得すべく下記のように定義したとします。

public static final long THIRTY_ONE_DAYS_MILL_SEC = 31 * 24 * 60 * 60 * 1000;

この数字は、2,678,400,000です。
26億7840万ミリ秒ですね。

ですが、これを実行すると、実際には、THIRTY_ONE_DAYS_MILL_SEC

-1616567296 

という数字が入っています。マイナスの数字になってしまってるんですよー。

なんで?というと、次のようなことが起きているらしい。

①右辺で計算する時はintで計算されている

②intの最大値が int32ビット-2,147,483,648 〜 2,147,483,64

③桁あふれでマイナス値になってしまっている

なので、右辺で計算するときにlongであると宣言してあげないと、 2,678,400,000 にならないらしいのです…。

末尾にLをつけるだけ。

正しくは、下記の通り。


public static final long THIRTY_ONE_DAYS_MILL_SEC = 31 * 24 * 60 * 60 * 1000L;


Ghost Inspector テストの共通化

弊社では、製品の自動テストにGhost Inspectorを利用しています。

ある日のことです。

「この画面消して新しい画面に置き換えるからテストの方も修正しといてよ」

OKまかせろ。

意気揚々とGhost Inspectorのダッシュボードを開きます。

コピペで複製されたものに微妙に改変が加わったり加わらなかったり無造作に乱立したガバガバテスト群を目撃したぼく「がっでむ」

キレた

プログラミングを行う際、共通の処理は関数化を行うなど、なるべく記述を重複させないという方針がございます。
いわゆるDRY原則というやつですね。(厳密には、よく聞くDRY原則は誤認されたものだと言われていますが、話が逸れるので適当なネット記事に任せます)

コードを書いている感覚の少ないGhost Inspectorだって、共通した処理の再利用は重要です。

  • メンテナンスコストの軽減
  • テスト修正時の修正漏れの防止
  • 再利用性の向上
  • 似ているテスト同士の差分確認の容易化

あたりの利点はパッと思いつきますね。特に上2つが現場的には嬉しいところです。

本題

実際に共通化を行っていきます。
今回は、「元々(ほぼ)同じ内容のテストがコピペ乱立していたものを、1つの共通テストを再利用する形に修正し、メンテナンスコストを軽減する」といったシナリオです。泥臭い箇所もございます。

0. 作業フローの確認

  1. 共通化を行いたいテスト内容を持つ、テストA, Bの差分を確認する。
  2. 共通部分を別の「共通テスト」とし、可能なら差分箇所を抽象化する。
  3. テストA, Bは共通テストを参照してテストを行う形に変更する。抽象化した箇所(認証情報やアドレス等)を適宜指定してテストが走るようにする。

1. テスト内容の差分確認

いきなり技術力のカケラも感じないアナログチェックです。

2つのテスト内容を見比べます。

差分のチェックにはdifff《デュフフ》を利用しました。

差分が存在しない場合は、素直にテストを DuplicateMoveしてしまえばOKだと判断できます。

テストの内容に差分が生じている(=異なるテスト項目がある)場合は、内容を吟味して共通テストが吸収したりしなかったりするだけです。何も恐れることはありません。

2. 「共通テスト」の作成

差分の確認ができたら、次は共通で利用するテストを作成します。

共通のテスト項目となる箇所を、個別のテストとして保存します。
DuplicateMoveするなりうまくやりましょう。
わかりやすく「共通テスト」などといったTest Suiteに放り込むと良いでしょう。

テストA, Bそれぞれで利用することになるため、ログイン認証など、テストごとに異なる可能性のある箇所は変数を利用して抽象化します。

参考:Creating and Using Variables – Ghost Inspector

  • テストAとテストBで、同じ名前の変数に値を入れる。
  • 共通テストでは、変数を参照するのみにする(値を入れない)。

このようにすることで、「共通したテスト項目を、複数のアカウントでテストする」といったことが簡単に実現できます。

3. 共通テストを利用したテストの実行

「共通テスト」の作成が完了したら、作成した共通テストを読み込んでテストを実行しましょう。

参考:Modularizing Tests – Reusing Test Steps – Ghost Inspector

Import steps from testすれば、他のテストからテスト項目を読み込んでテストを走らせることができます。

これにより、ログイン情報のみテストA, Bで入力(変数に値を設定)し、画面上のフォームへ値を投入する共通の処理は共通テストで一本化するといったことが可能になります。

このとき、変数に値を設定する処理をテストごとに記述してもいいのですが、Test Suite単位で使うアカウントがほぼ固定であるようなケースも多いと思います。
そういったケースでは、Suite-level Variablesを設定しましょう。より管理が簡単になります。

変数を指定して問題なく動けば作業完了です。

4. その他

テスト毎のStart URLを個別に指定するのも煩雑です。
Suite SettingsTest Defaultsから、Suite単位で共通のStart URLを設定することができます。

※各テストでStart URLを指定しなかった場合のデフォルトの値を指定するだけなので、テストによって別途Start URLを指定している場合でも問題は生じません。

Ghost Inspectorのテスト管理がぐちゃぐちゃで参った人は参考にしてみてください。

※もちろん、ログイン認証部分のテストも共通化してImport steps from testするとよりスマートなのだろうと思いますが、弊社はそこまで綺麗に整えていません……

Android エミュレーターで通信速度の遅い端末を作る Android Studio3.3.2

次の手順で行います。

Tools→

AVD Manager→

各エミュレーターの一覧出てくると思いますが、その鉛筆みたいなアイコンをクリック→

「Show Advanced Settings」をクリック

Netoworkという項目にSpeedとLatencyというのがありますので、それを変更。

・Fullがデフォルトで、PC上でできるかぎりのネットワーク速度を実現

・LTE なんか懐かしい  略称はLong-Term Evolution  らしい。

・HSDPA 多分、昔ハイスピードパケット通信とか言ってたあたりだと思うんですよね。この辺りから、遅さを体感できる…。

・GPRS 3Gぐらいの通信速度

下記の公式情報も参考にしてください。

https://developer.android.com/studio/run/managing-avds?utm_source=android-studio