import me

"あなたの当たり前が誰かのためになる"

Androidアプリ設計パターン入門を読んで: #1

はじめに

今回はPEAKSから発売されているAndroidアプリ設計パターン入門を先日購入しまして、ある程度読んだので未熟者ですが所感や学びになったことについて書きたいと思います。

peaks.cc

 

 先に結論を言うと、Android開発に携わっている人なら買ったほうがよい!と思いました。僕のようにあまり詳しくない人にも分かるようにサンプルをもとに詳しく書かれています。

 

 とりあえず読んだところで言語化できるところを少しづつ書いていきたいと思います。

2/18時点で読み終わったところです。電車の行き来などで読んでましたが、結構ボリュームがありました。

 

第一部: アプリの設計を知る

 第一部では、基本的な構成として採用されているアーキテクチャについて説明されていました。

 MVPやMVVMについて基本的な概念からTODOアプリを使っての説明がされていました。

 

MVPとは?

Model - View - Presenterの略でUIの複雑さを解決することを目的とするアーキテクチャです。

MVPについては以下のように解説してくれているサイトもあります。

dev.classmethod.jp

MVCは以前の現場でやっていたのですが、今はMVPの構成で書いていてなんとなく雰囲気でやっていた節があったので自分にはこの第一部の内容は勉強になりました。

 

MVVMとは?

Model - View - ViewModelの略で、MVPに似ているのですが、データバインディングを使ってViewとViewModelで双方向のやり取り?がある点が特徴的だなと思います。

やり取りというと違う気がしますがうまく言語化できないです。。。

Qiitaにもわかりやすい記事があったのでリンクを共有します。

qiita.com

データバインディングに対する理解がまずないとこのアーキテクチャに対する理解が難しいのではと思います。開発に携わる者がデータバインディングを多少は学習しておく必要があるのではと感じました。

 

第一部を一通り読んでみて

ロジックを切り離すという点では、MVP、MVVMはほぼ同じなのかなと思いますがMVVMの場合はプロジェクトに参加しているエンジニアがDataBindingに対する理解をきちんとしていないと効率が悪くなるなと感じました。

僕自身DataBinding初心者なのでMVVMに入る前に学習が必要だと思います。

 

実際に開発に携わる中でMVPでアプリを作っていく経験をしているので、確かに今のAndroid開発において本当に基本的な知識なのだなと実感しました。

サンプルアプリを基に丁寧に紹介されており非常にわかりやすかったです。

 

github.com

上のリンク先をみて引き続き勉強していきます。

 

さいごに

小並感になってしまった感がありますが、買ってよかったと思います。納得しながら読み進めることができました。

 

ぜひ本を購入して読んだ方がいいと思います。

3千円ちょっとで購入できる割には、安いし内容が充実していて初学者ながらわかりやすいと思って読み進めることができました。

Amazonアカウントでも購入できるようですが、僕は普通にクレカで購入しました。

 

本で紹介されていたリンクを基に引き続きアンテナを張って勉強していきたいと思います。

 

第二部、第三部に関してはまた記事を書こうと思います。

 

SharedElementTransition: 基礎編

はじめに

今回はSharedElementTransitionの基礎編としてSharedElementを用いてマテリアルデザインに沿った遷移の基本をサンプルを用いて書き残しておこうと思います。

 

今回やっていることで参考にしている資料は、DroidKaigiで実例で理解するマテリアルデザインアニメーションというスライドです。

 

 

まず最初に実際にサンプルの動きを確認する

f:id:rozkey59:20180218132655g:plain

 

FABをボタンにして今回はやっています。

Activityから次のActivityに遷移する際に拡大しながらアニメーションするものです。

どのように実現するのか実際のコードを見ていきましょう。

 

遷移を実現するためのポイント

この遷移を実現するのに必要なポイントは以下の2点です。

  1. レイアウトにtransitionNameをつける
  2. 必要な情報をBundleに詰めて渡す

実際のコードを見ていきましょう。

 

レイアウトにtransitionNameをつける

今回は画像のViewを共有したいのでレイアウトにユニークな名前を設定してあげる必要があります。遷移前と遷移後のレイアウト両方で設定する必要があります。

 

content_main.xml

ちょっとややこしいですが遷移前の小さい画像のレイアウトの部分になります。

<layout xmlns:tools="http://schemas.android.com/tools">

<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:transitionName="@string/transition"
android:src="@drawable/android"
tools:ignore="ContentDescription"/>

</android.support.constraint.ConstraintLayout>
</layout>

ImageViewの中でtransitioNameをつけています。名前はどんなものでもOKです。

xmlの中で設定しても良いですが、コードで指定することもできます。

自分の場合は分かりやすいのでxmlで完結しています。

 

activity_next.xml

遷移後の画面になります。

<layout xmlns:tools="http://schemas.android.com/tools">

<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:transitionName="@string/transition"
android:src="@drawable/android"
tools:ignore="ContentDescription"/>

</android.support.constraint.ConstraintLayout>
</layout>

こちらは画像が大きくなった時のImageViewに遷移前と同じtransitionNameを設定してあげています。

 

strings.xml

transitionNameに設定している名前です。

直に設定するよりここで設定してここからxmlで定義するのが良いと思います。

<resources>
<string name="app_name">SharedElementTransition</string>
<string name="action_settings">Settings</string>
<string name="transition">transition:android</string>
</resources>

 

必要な情報をBundleに詰めて渡す

遷移前で以下のように必要な情報をBundleに詰めてあげて渡してください。

binding.fab.setOnClickListener { view ->
val bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(
this,
binding.contentMain?.image,
getString(R.string.transition)
).toBundle()
ActivityCompat.startActivity(
this,
Intent(this, NextActivity::class.java),
bundle)
}

fab(フローティングアクションボタン)をタップしたときにbundleを作ってここで必要な情報を詰めます。一つの場合はこれだけで良いです。

 

defaultで設定されているので広がるアニメーションをつける場合はたったこれだけで上記のような遷移が実現できます。

 

用意するActivityやレイアウトについて

一応サンプルのコードを載せておきます。

以下リンクからクローンしてビルドも出来ますのでやってみてください。

GitHub - yutaro6547/SharedElementTransition

Activity

遷移前のActivityと遷移後のActivityを用意してください。

僕の場合は、MainActivity(遷移前)とNextActivity(遷移後)のように用意しています。

MainActivity

今回はDataBindingを使用しています。

実際のコードは以下になります。

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
setSupportActionBar(binding.toolbar)
binding.fab.setOnClickListener { view ->
val bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(
this,
binding.contentMain?.image,
getString(R.string.transition)
).toBundle()
ActivityCompat.startActivity(
this,
Intent(this, NextActivity::class.java),
bundle)
}
}

override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
return true
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_settings -> true
else -> super.onOptionsItemSelected(item)
}
}
}

 

NextActivity

遷移後のActivityは表示するだけなのでこのぐらいです。

class NextActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_next)
DataBindingUtil.setContentView<ActivityNextBinding>(this, R.layout.activity_next)
}
}

 

レイアウト

activity_main

遷移前のレイアウトになります。

DataBindingを利用しているので<layout>でくくっています。

<?xml version="1.0" encoding="utf-8"?>
<layout>

<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="zukkey.sharedelementtransition.MainActivity">

<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay"/>

</android.support.design.widget.AppBarLayout>

<include
android:id="@+id/contentMain"
layout="@layout/content_main"/>

<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
tools:ignore="ContentDescription"
app:srcCompat="@android:drawable/ic_dialog_email"/>

</android.support.design.widget.CoordinatorLayout>
</layout>

 

content_main

メイン画面の中身のレイアウトになります。

<layout>

<android.support.constraint.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"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="zukkey.sharedelementtransition.MainActivity"
tools:showIn="@layout/activity_main">

<ImageView
android:id="@+id/image"
android:layout_width="100dp"
android:layout_height="100dp"
android:transitionName="@string/transition"
android:src="@drawable/android"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>

</android.support.constraint.ConstraintLayout>
</layout>

 

activity_next

遷移後のレイアウトになります。

<layout xmlns:tools="http://schemas.android.com/tools">

<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:transitionName="@string/transition"
android:src="@drawable/android"
tools:ignore="ContentDescription"/>

</android.support.constraint.ConstraintLayout>
</layout>

 

さいごに

今回はSharedElementTransitionの基本についての解説と備忘録でした。

スライドを基にやってみたという話ですが、実際に手を動かして書いてみると理解が深まるのでやってみてよかったです。

複雑なレイアウトになったり、複数共有する場合はもうひと手間必要ですが自分も試行錯誤中なので分かり次第またサンプルを作って投稿していきたいと思います。

 

 

RxKotlin:{Cold? or Hot?}編

はじめに

前回はRxJavaにおけるSchedulerについて書きました。

 

rozkey.hatenablog.com

 

今回はRxjavaにおける、Coldな生産者(Flowable / Observable)とHot(Flowable / Observable)な生産者についてお話します。

 

Coldな生産者とHotな生産者の違い

RxJavaにおいて生産者には二種類あり、Coldな生産者とHotな生産者があります。

 

Coldな生産者は、生産者と消費者は1対1の関係になり、Hotな生産者は、生産者と消費者は1対nの関係になります。

 

Coldな生産者の場合subscribeされるたびにデータストリームとなるタイムラインが形成されますが、Hotな生産者の場合は1つのタイムラインからしかデータを通知されず複数の消費者がsubscribeして購読することが可能です。

 

画像で対比してみると以下のようなイメージです。

雑にですが作成してみました。

Coldの場合

f:id:rozkey59:20180217145337p:plain

Hotの場合

f:id:rozkey59:20180217145839p:plain

Coldの場合は別のデータストリームで処理を行うことが予期されますが、Hotの場合は消費者が複数購読できるので、途中から同じデータを取り出して処理を行いたいなどの時に使えると思います。

HotなFlowable / Observable

HotなFlowable / ObservableはColdなFlowable / Observableから変換するオペレータによって作り出すことができます。

 変換するためのオペレータは、publish、replay、shareがありますので今回はそちらのオペレータについての理解を書き残しておきたいと思います。

 

ColdからHotに変換するオペレータ

publish

publishメソッドではColdなFlowable / ObservableをHotなFlowable / Observableに変換できるオペレータです。

このメソッドでConnectableFlowable / ConnectableObservableが生成され(HotなFlowable / Observable)、この後に購読した消費者に対してデータ通知を行います。

 

replay

replayメソッドは基本的にpublishと同じですが、最初にデータをキャッシュしておき新たに購読した消費者に対して最初にキャッシュしたデータを通知してそのあとはpublishと同じです。

 

share

shareメソッドではColdなFlowable / ObservableからHotなFlowable / Observableを生成することができます。このメソッドではConnectableFlowable / ConnectableObservableを生成しないというのが特徴です。購読している消費者がいる間は複数の消費者に対して同じデータを通知できます。

 

さいごに

今回はColdな生産者とHotな生産者の違いについてと変換して生成するためのオペレータに対する理解を自分なりにまとめてかみ砕いてみました。

今度は実際にRxをアプリにどう生かしたり使ったりしていけばいいのかというものをサンプルアプリを作って説明していきたいと思います。

 

 

 

大学と院と就職の選択について

はじめに

今回は、とりあえずできるだけ毎日投稿していこうと思ったものの中途半端な状態で間に合いそうになく、ちょうど卒業間近なので大学時代を振り返ってみる。

(毎日投稿をちょっとごまかしてる感があって罪悪感があるけれど、、、間に合わなかったのです)

 

特に技術的なことは話さないのとエモい話もないです。

単純にもっとこうしておけばよかったなとかどうして就職を決めたのか何となく書き残しておきます。

 

大学という選択

大学進学を決めたのは、進学校にいたのでみんな大学に進むということが当たり前だったし大学を出ておけば就職も良いという周りの流れと家族の勧めで決めました。

どこの大学に行くかどの学科に進むのかというのは自分の意思で決めたものでしたが今思うと流されていたなぁと思います。

大学に入ってみて

結果的に言うと僕個人としては、良くも悪くもなかったです。

なぜかというと、大学は学問をするところであり、自分の目的としては学問をするためというよりかは就職のため必要だからという理由でした。

もちろん当時から興味がある分野を選んだものの、アカデミックに突き抜けていきたいという気持ちはなかったです。

 

 

大学に入ったメリットは人脈ができること時間があること関東に来たことだと思います。

デメリットは私学の場合はお金がかかることです。

 

 

人脈ができることについて

全国からいろんな考えを持った人が集まってくるので自分の知らないことを知っている人に出会えるのが大学の面白いところだと思います。

ハードウェアに精通しておりPCに100万投資してきた人の話を聞けたり、やたらとC言語に詳しい人に出会えたりなどいろいろな人が集まってくるのでそこで新しい考え方を知る機会があるだけでも大きいと思います。

 

 

時間があることについて

大学生は基本的に時間があります。

理系であっても普通に単位を取得していれば3年生は大体暇になります。

褒められたことではないですがテストだけ出て単位をとれることもありますので比較的時間が取れます。

研究室によっては院に行くことが必須条件で研究室にこもりきりというところもありますが、人によって選択は変えられます。

 

時間ができると自分が興味のある勉強に費やすことができますし、自分の場合は復習も兼ねてアプリを取り合えず作ってみたりしていました。

 

最初の頃は写経から始めましたがオリジナルアプリも作ったりしていました。

1,2年では難しかった長期インターンに参加することもできますし英語や海外の文化を知りたいのであれば留学もできると思います。

友達が一か月カナダ留学に行ってましたがやはり刺激や得るものは多かったみたいです。ただ、一か月では英語は身につかなかったようですが。

 

関東に来たことについて

一番良かったのが関東に来たことでした。

親の仕事の都合もあり関東に来たのですが、結果として来てよかったです。

関東に来ると選択肢が大幅に増えます。

勉強会や技術イベントに気軽に参加することができますし、長期インターンをやっている企業も多いです。

ベンチャーでは人手不足であったり、諸々の都合上やる気のある学生のインターンを優遇して取ってくれることもあります。入るのが難しいところもあるのでケースバイケースです。

地元にいたら、まずできなかったことなので、関東に出てきて一番良かったことだと思いました。

 

お金がかかることについて

私学に入ったのは失敗だったと思いました。

正直関東で行ける国立はあったものの、高望みして別の国立を受けてしまい、落ちたので私学に行くことになったのですが、実家から通っているとはいえ学費がとても高いです。

大卒という肩書自体は、どこの大学を出ても同じ( 就職の時は場合による )なので今思えば関東の行ける国立を受けておけば良かったと思いました。

 

大学の時にもっとこうしておけばよかったと思ったこと

1年から長期インターンに参加していれば良かったと思います。

エンジニアとして成長していくためには、僕は実戦で学ぶことと、いいエンジニアのコードを読むこと、そしてコードレビューしてもらうことだと思います。

大学でプログラム勉強してましたでは正直微妙です。

競技プログラミング出てましたとかいう人は別ですが、アルゴリズムの実習だけだと実力は未知数かなと思います。

でも、挑戦していけばいいのでとりあえずやってみるってことが大切だと思います。

 

最初はベンチャーを探していなくて大きいところばかり見ていたのでやりたくてもやれないというのと情報を得るのが下手だったということが大きかったと思います。

また、自分の場合はサークルに参加していなかったため余計情報がなかなか入ってこなかったなと思います。

もっと調べて挑戦していくのを早めにやっておけばよかったなと後悔しています。

 

大学院進学ではなく就職という選択

そもそも経済的な余裕がなかった

大学自体、親が家を購入するために溜めていた貯金で僕に投資してくれました。

そのため奨学金は使わず、自分もバイトを掛け持ちして交通費や食費程度は出していました。

 

院に進学する余裕はないと親に言われていたことと、自分自身、奨学金という名の借金を背負ってまで大学院に行って研究をしたいというわけではなかったです。

親にいらないと言われましたが、浪人したりしてわがままを言ってしまったので学費を返したいという思いもあり大学院は考えてませんでした。

 

 

それとは別に実際に働いてみて楽しいから

アプリを作って実際に動いたとき、できた!という感覚が面白くてついつい人に喜んで見せてしまうということもあり、楽しんでやっています。アプリのレビューには酷評を書かれたこともありますが、使ってもらってよくなったとか言われるだけで嬉しいです。

お金を稼ぎたいという気持ちもあるし、働いていて楽しい方がいい!ということもあり就職しようと決めてました。

 

おわりに

見ていただいてありがとうございます。

簡単に大学から今までを振り返っただけですが、明日からはまた技術投稿に移りたいと思います。

 

RxKotlin: Scheduler{subscribeOn, observeOn}編

はじめに

前回はRxJavaにおける合成オペレータについてサンプルを用いて解説しました。

その記事は以下の通りです。

rozkey.hatenablog.com

 

今回は、RxJavaにおけるSchedulerについてでsubscribeOnメソッドとobserveOnメソッドの違いについてお話します。

 

Schedulerとは?

RxJavaで用意されているスレッドを管理するクラスのことです。

スレッドとは

threadとは、糸とかいう意味でプログラミング的には処理の始めから終わりまでの実行の流れのことをいいます。詳しくは下記参照。入門てきな技術書にも載ってます。

www.itsenka.com

 

www.sejuku.net

 

僕もそういえば初めてオリジナルアプリ作ったときにやったなぁとふと思い出した。(小並感)

 

スレッドはプログラミングを勉強し始めたばかりだとほぼほぼ使うことが無かったりするのですが、実際に開発をやる際には気を付けることが多々あります。

 

例えば、通信を行うスレッドとUIの描画を行うスレッドなどを一緒にしてはいけません。描画が遅くなってパフォーマンスが悪くなったりするからです。僕は一度やって失敗した経験があります。(笑)

 

話を元に戻します

RxJavaでは時間を扱う処理をしなければ、デフォルトのスレッドですべての処理が行われます。

また扱うメソッドによってはデフォルトでメインのスレッド上とは異なるスレッドで処理を行ってくれる生産者もあります。

前回の記事の中で使用していたintervalメソッドなどです。

スレッドを受け取ったデータに対してどのように処理をするのかについてわかっていないと僕のような失敗を引き起こすことがあります。

なのでまずは、subscribeOnとobserveOnメソッドの違いについて見ていきましょう。

 

subscribeOnメソッド

subscribeOnではObservableの処理をどのScheduler上で行うか設定できます。

実際にサンプルプログラムを見ていきましょう。

fun main(args: Array<String>) {
val array: Array<String> = arrayOf("allow", "bow", "", "dead")
Observable.from(array)
.subscribeOn(Schedulers.computation())
.subscribeOn(Schedulers.io())
.subscribeOn(Schedulers.immediate())
.subscribeOn(Schedulers.trampoline())
.subscribe{data ->
val thread = Thread.currentThread().name
println(thread + ": " + data)
}
Thread.sleep(1000L)
}

こんかいはいつも通りに文字列のObservableにたいしていくつかのSchedulerを設定してみます。スレッドの名前を取得して表示するようにしてあります。

 

実際の実行結果が以下になります。

f:id:rozkey59:20180214233025p:plain

 

subscribeOnメソッドでは一度設定するとそれ以降Schedulerを設定することはできません。またデフォルトの設定があるintervalメソッドでも設定することはできません。

 

実際にサンプルを見ていきましょう。

fun main(args: Array<String>) {
Observable.interval(500L, TimeUnit.MILLISECONDS)
.take(10)
.subscribeOn(Schedulers.io())
.subscribeOn(Schedulers.immediate())
.subscribeOn(Schedulers.trampoline())
.subscribe{data ->
val thread = Thread.currentThread().name
println(thread + ": " + data)
}
Thread.sleep(1000L)
}

intervalメソッドでObservableを生成し、10件だけ取ってきてSchedulerを他に設定してみます。

この実行結果が以下になります。

f:id:rozkey59:20180214233710p:plain

やはりデフォルトで指定されている場合もSchedulerの変更ができないことが分かります。

では、処理の途中でSchedulerの変更を行いたい場合はどうすればよいでしょうか?

それが次に紹介するobserveOnメソッドになります。

 

observeOnメソッド

observeOnメソッドでは、受け取ったデータの処理をどのSchedulerで行うのかを設定することが出来ます。subscribeOnとの違いはSchedulerの設定を変更することができる点です。

 

実際のサンプルプログラムを見ていきましょう。

fun main(args: Array<String>) {
val array: Array<String> = arrayOf("allow", "bow", "", "dead")
Observable.from(array)
.observeOn(Schedulers.computation())
.observeOn(Schedulers.io())
.observeOn(Schedulers.immediate())
.observeOn(Schedulers.trampoline())
.subscribe{data ->
val thread = Thread.currentThread().name
println(thread + ": " + data)
}
Thread.sleep(1000L)
}

さっきsubscribeOnメソッドで説明していたサンプルでsubscribeOnをobserveOnに変更してやってみました。

 

実行結果が以下になります。

f:id:rozkey59:20180214234447p:plain

実行結果を見てみると、確かに最初に設定したSchedulerと異なるSchedulerを設定できていることが分かります。

 

では、デフォルトで設定してあるSchedulerをもつintervalメソッドでのObservableについてはどうなのでしょうか?

 

実際にサンプルを見ていきましょう。

fun main(args: Array<String>) {
Observable.interval(500L, TimeUnit.MILLISECONDS)
.take(10)
.observeOn(Schedulers.io())
.observeOn(Schedulers.immediate())
.observeOn(Schedulers.trampoline())
.subscribe{data ->
val thread = Thread.currentThread().name
println(thread + ": " + data)
}
Thread.sleep(1000L)
}

これもさきほどのsubscribeOnの時と同じでsubscribeOnをobserveOnに変更してやってみました。

 

実際の実行結果が以下になります。

f:id:rozkey59:20180214234944p:plain

defaultで指定されている場合でもobserveOnメソッドではSchedulerを変更することが可能です。

 

おわりに

今回はRxJavaにおけるSchedulerとsubscribeOn, observeOnの違いについてお話しました。

まだまだ知ることがあるのですが今日のところはここまでにします。

また次回にSchedulerについて学んだことを書き残していきたいと思います。

 

 

 

RxKotlin:{Merge, Zip, CombineLatest}編

はじめに

前々回はこちらについて書き、

rozkey.hatenablog.com

 

前回はこちらについて書きました。

rozkey.hatenablog.com

 

今回はその続きで、RxJavaにおける合成オペレータの紹介です。

 

合成オペレータとは

勝手に呼んでいるのですが、(公式にあるのかな?ちょっと調べてみないと分からん)複数のFlowable / Observableに対して1つにまとめたり揃ったタイミングで通知したりするオペレータのことを僕はそう呼んでます。

 

Merge

Mergeメソッドは、複数のFlowable / Observableを1つにまとめて同時に実行するオペレータです。複数のデータの流れを一つにまとめることができます。

サンプルプログラムを見ていきましょう。

fun main(args: Array<String>) {
val ob1 = Observable.interval(500L, TimeUnit.MILLISECONDS)
.take(10)
.map { data -> data.toString() + "A" }
val ob2 = Observable.interval(300L, TimeUnit.MILLISECONDS)
.take(20)
.map { data -> data * 3 }

Observable.zip(ob1, ob2, {data1, data2 -> Arrays.asList(data1, data2)})
.subscribe({
println("Zip data : " + it.toString())
}, {})
Thread.sleep(10000L)
}

どっちの値か分かりやすいように変換を行っています。

 

500ミリ秒ごとに数値を生成して10件のみAという文字列を加えて文字に変換したObservableと、300ミリ秒ごとに数値を生成して20件のみ3倍にしたObservableを合成して同時に実行するサンプルプログラムです。

 

実行結果は以下になります。

f:id:rozkey59:20180211192954g:plain

若干カーソルがウザイ感じがしますが気にしないでください。

確かに二つのObservableで生成されたデータが一つに結合されて同時に一つのObservableにて実行されていることが分かります。

 

Zip

Zipメソッドは、複数のFlowable / Observableの通知データが揃ったタイミングで新しいデータを生成することができます。

サンプルプログラムを見ていきましょう。

fun main(args: Array<String>) {
val ob1 = Observable.interval(500L, TimeUnit.MILLISECONDS)
.take(10)
.map { data -> data.toString() + "A" }
val ob2 = Observable.interval(300L, TimeUnit.MILLISECONDS)
.take(20)
.map { data -> data * 3 }

Observable.zip(ob1, ob2, {data1, data2 -> Arrays.asList(data1, data2)})
.subscribe({
println("Zip data : " + it.toString())
}, {})
Thread.sleep(10000L)
}

今回は 500ミリ秒ごとに数値を生成して10件のみAという文字列を加えて文字に変換したObservableと、300ミリ秒ごとに数値を生成して20件のみ3倍にしたObservableをzipメソッドを読んで実行するサンプルプログラムです。

 

Mergeの時と比較するために最初に用意しているObservableを変えていません。

実行結果が以下になります。

f:id:rozkey59:20180211193438g:plain

Mergeと比較すると確かにデータがそろったタイミングで新しいデータを生成できていることが分かります。逆にデータがそろわない場合は生成していないことが分かります。

 

CombineLatest

CombineLatestメソッドでは、複数のFlowable / Observableがデータを通知するたびに新しくデータを生成しています。

サンプルプログラムを見ていきましょう。

fun main(args: Array<String>) {
val ob1 = Observable.interval(500L, TimeUnit.MILLISECONDS)
.take(10)
.map { data -> data.toString() + "A" }
val ob2 = Observable.interval(300L, TimeUnit.MILLISECONDS)
.take(20)
.map { data -> data * 3 }

Observable.combineLatest(ob1, ob2, {data1, data2 -> Arrays.asList(data1, data2)})
.subscribe({
println("CombineLatest data : " + it.toString())
}, {})
Thread.sleep(10000L)
}

zipの時と変わったのがcombineLatestの部分だけです。

Zipとどう違うのか比較するために実行結果を見てみましょう。

f:id:rozkey59:20180211210830g:plain

片方のObservableの通知があるたびに新しいデータが生成されていることが分かります。zipでは揃わないと新しいデータが生成されなかったのに対して、どちらか片方通知があることで新しくデータを生成していくことができます。

 

まとめ

今回は合成オペレータのMerge, Zip, CombineLatestについての紹介でした。

実際にAPIから取ってきたデータを扱う際に有効的に使えそうだなとサンプルを作成して動かしてみて思いました。

今回使用した例に関しては、どうしても合成するタイプのオペレータは例が作り辛かったので値や処理等は変えていますが下記資料を参考にしています。

下記の本はRxJavaを理解するのに非常にわかりやすいので、もっと詳しく知りたい方は是非購入して読んでみてください。

RxJavaリアクティブプログラミング 著:須田智之

 

実例で学ぶBottomNavigationView

はじめに

突然ですが、皆さんは画面下部につけるタブバー?はどのように実装されていますか?

 

僕は以前はGridViewをなんとか使って実装していました。

サードパーティーのライブラリを使って実装している人もいると思います。

 

ですが、Androidには標準で用意されているBottomNavigationViewというものがあります。

今回はそのBottomNavigationViewについての紹介です。

 

今回話す内容は、

 ・BottomNavigationViewのサンプルの作成の仕方

 ・タブの幅を一定にする方法

 ・タブアイテムの文字の表示 / 非表示の仕方

について話します。

 

今回作成したサンプルはGitHubにも上げているので作成するのが面倒な人はクローンしてビルドしてください。

GitHub - yutaro6547/BottomNavigationSample

 

まずはプロジェクトを作成します

BottomNavigationViewを学ぶにはサンプル自体が標準で簡単に作成できるので普通に作って動かしてみましょう!

くどいくらい初っ端から説明していくのでプロジェクトの作り方を知っている人はこのセクションは飛ばしちゃってくださいな!

 

ともかくプロジェクトを作る

f:id:rozkey59:20180211220343p:plain

 File > New > New Projectから作成できます。

ApplicationNameには好きな名前を付けてください。Company domainも同様に。

Kotlinで書くので入っていなければ、Include Kotlin supportにチェックを入れておいてください。

 

つぎにターゲットバージョンを選ぶ

f:id:rozkey59:20180211220720p:plain

APIのレベルによってどのバージョンから対応するか選んだり端末など選べる画面です。今だと古い端末だと4系使っている人もいるのですがLolipopからが多いのでここは気にせずNextを押していきましょう。

 

今回はBottomNavigationActivityを選択する

f:id:rozkey59:20180211220922p:plain

BottomNavigationViewの機能の確認をしたいので、上の画像のように選択してください。勝手にある程度用意されたサンプルが作られた状態で始められます。

 

最初の画面の選択

f:id:rozkey59:20180211221109p:plain

ここは特に何も考えずFinishを押しましょう!名前を変えたい場合は変えてもよいですがそのままでOKです。

Finishを選択することでプロジェクトが勝手に作成されます。

 

通常状態

さて、上記のように作成し終えてビルドすると下記のようにサンプルを作成することができると思います。

f:id:rozkey59:20180211221309g:plain

エミュレータ上で実行すると上記のように表示が確認できサンプルを動かすことができます。

ここからちょっとずつカスタムしていきましょう!

 

まずはタブのアイテムを一個増やす

イコン画像を適当に用意しましょう

material.io

上のサイトから適当にアイコンをダウンロードしてきてください。

svgが良いと思います。

app > res > drawableを右クリックしてNew > Vector Assetからさっきダウンロードしたやつを追加することができます。

f:id:rozkey59:20180211222650p:plain

上記画像のように選択し終えたらnextを押してfinishを選択することで画像を入れられます。

 

そのあとは、app > res > menu > navigation.xmlを下記のように変更してください。

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

<item
android:id="@+id/navigation_home"
android:icon="@drawable/ic_home_black_24dp"
android:title="@string/title_home"/>

<item
android:id="@+id/navigation_dashboard"
android:icon="@drawable/ic_dashboard_black_24dp"
android:title="@string/title_dashboard"/>

<item
android:id="@+id/navigation_notifications"
android:icon="@drawable/ic_notifications_black_24dp"
android:title="@string/title_notifications"/>

<item
android:id="@+id/navigation_android"
android:icon="@drawable/ic_android_black_24px"
android:title="@string/title_android"
/>

</menu>

 icon名はさっきのとこで指定したものを、idも適当につけちゃって良いです。

titleはstring.xmlのなかで新たに指定するかすでにあるものを利用してください。

app > res > values > string.xmlにて

<resources>
<string name="app_name">BottonNavigationSample</string>
<string name="title_home">Home</string>
<string name="title_dashboard">Dashboard</string>
<string name="title_notifications">Notifications</string>
<string name="title_android">Android</string> //追加
</resources>

こんな感じで好きな名前を付けて追加しましょう。

app > java > xxx.yyy(パッケージ名)の中にあるMainActivity.ktにて下記のように追加します。

private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.navigation_home -> {
message.setText(R.string.title_home)
return@OnNavigationItemSelectedListener true
}
R.id.navigation_dashboard -> {
message.setText(R.string.title_dashboard)
return@OnNavigationItemSelectedListener true
}
R.id.navigation_notifications -> {
message.setText(R.string.title_notifications)
return@OnNavigationItemSelectedListener true
}
// 追加
R.id.navigation_android -> {
message.setText(R.string.title_android)
return@OnNavigationItemSelectedListener true
}
}
false
}

ここではidを指定することで条件分岐を行っており、タップした際にviewの中で表示されているテキストを変えるということを行っています。

 

これでようやく一つのタブアイテムを追加することができます。

 

ここまで来てビルドをすると、以下のようになります。

f:id:rozkey59:20180211223517g:plain

自分で設定したアイコン画像と文字が表示されていることが確認できます。

 

同じようにしてもう一つ追加してみる

先ほどと同じような流れで一通り変更してみると以下のようになります。

f:id:rozkey59:20180211223804g:plain

こうしてみるとタブを選択したときに幅が広がって気持ち悪い感じがしますよね?

 

3つの時は問題なかったのですが、タブのアイテムが増えたときに選択したアイテムが広がってしまうのを防ぎたかったりしたいし、名前とか表示したくないとか色々とカスタムしたいと思います。

 

なので、今回は以下の内容を解説していこうかなと思います。

 ・タブの幅を一定にする

 ・選択したときのアイテムのタイトル名を消す

 ・逆に選択に関わらずアイテムのタイトル名を表示する

 

タブの幅を一定にする

タップした際に選択したタブの幅が広がってしまうのを防ぐということをやっていきたいと思います。

そのためにはカスタムViewを作る必要があります。

stackOverFlowでググると下記のように出てきますので同じことをやって多少変更を加えていきます。

stackoverflow.com

 

まずは適当にクラスを作ります。

f:id:rozkey59:20180212115637p:plain

白い矢印のとこで右クリックしてNew > Kotlin File / Classから作成します。

名前は何でも良いです。僕はCustomBottomNavigationViewにしました。

 

本来ならui > component > viewなどディレクトリを分けたりした方が良いですが、今回は学んでいくということが目的なので適当です。

 

下記のようにカスタムしたクラスを作っちゃってください!

class CustomBottomNavigationView : BottomNavigationView {

constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
disableShiftMode()
}

private fun disableShiftMode() {
val menuView = getBottomMenuView()!!
try {
val shiftingMode = menuView.javaClass.getDeclaredField("mShiftingMode")
shiftingMode.isAccessible = true
shiftingMode.setBoolean(menuView, false)
shiftingMode.isAccessible = false
} catch (e: NoSuchFieldException) {
Log.e("FieldError", e.message)
} catch (e: IllegalAccessException) {
Log.e("IllegalAccessError", e.message)
}
}

private fun getBottomMenuView(): BottomNavigationMenuView? {
var menuView: Any? = null
try {
val field = BottomNavigationView::class.java.getDeclaredField("mMenuView")
field.isAccessible = true
menuView = field.get(this)
} catch (e: NoSuchFieldException) {
Log.e("FieldError", e.message)
} catch (e: IllegalAccessException) {
Log.e("IllegalAccessError", e.message)
}
return menuView as BottomNavigationMenuView?
}
}

フィールドにアクセスしてリフレクションを使うことで無効化しています。

 

リフレクションとは?

プログラム自身の構造を読み取ったり書き換えたりすることです。

リフレクション (情報工学) - Wikipedia

 

元のコードを書き換えているのであまりやらない方がいいことですが、現状BottonNavigationViewのアニメーションをしないようにするにはこの方法しかないようです。DroidKaigi2018のアプリでもこの方法をとっていたと思います。

 

カスタムしたBottomNavigationをactivity_main.xml(BottomNavigationViewを使用しているレイアウト)の中で使用しましょう!


<android.support.design.widget.BottomNavigationView
android:id="@+id/navigation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="0dp"
android:layout_marginStart="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/navigation"/>

 xmlの中でこの部分を下記のように変更します。

//変更
<zukkey.bottonnavigationsample.CustomBottomNavigationView
android:id="@+id/navigation"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="0dp"
android:layout_marginStart="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:menu="@menu/navigation"/>

一行目だけ変えました!

 

これで実行をすると下記のようになります。

f:id:rozkey59:20180212123027g:plain

 

stackOverFlowのようにやる方法だとcompanion objectの中でdisableShiftModeを作成して任意のタイミングで呼び出す必要があったりしますが先ほどのように内部で完結させてあげるとxmlを変更するだけで良いです。

 

リフレクションを使用しているので、proguardで難読化のルール追加を忘れずに行いましょう。

Projectの中のBottomNavigationSample > app > proguard-rules.proに下記のように付け加えてください。

-keepclassmembers class android.support.design.internal.BottomNavigationMenuView {
boolean mShiftingMode;
}

これをしないとproguardでmShiftingModeフィールドを難読化する場合は機能することはありません。

 

選択したときにタブアイテムの文字を非表示にする方法

選択したときにアイコンだけにしたい人もいると思います。

その方法についてです。先ほど、CustomしたBottomNavigationViewクラスにて以下の部分を付け足すことで可能になります。

@SuppressLint("RestrictedApi")
private fun disableSmallLabel() {
val menuView = getBottomMenuView()!!
try {
for (i in 0 until menuView.childCount) {
val itemView = menuView.getChildAt(i) as BottomNavigationItemView
itemView.setShiftingMode(true)
itemView.setChecked(false)
}
} catch (e: NoSuchFieldException) {
Log.e("FieldError", e.message)
} catch (e: IllegalAccessException) {
Log.e("IllegalAccessError", e.message)
}
}

onMeasureメソッドで作成したdisableSmallLabelを呼び出すことを忘れないでください。

こうすることでタブアイテム下部のテキストを非表示にすることができます。

実際に動かしてみたものが以下になります。

f:id:rozkey59:20180212131145g:plain

 

 

逆に選択に関わらずタブアイテムの文字を表示する方法

タブアイテムに文字をずっと表示させたい場合は、CustomしたBottomNavigationViewクラスにて以下の部分を付け足すことで可能になります。


@SuppressLint("RestrictedApi")
private fun showSmallLabel() {
val menuView = getBottomMenuView()!!
try {
for (i in 0 until menuView.childCount) {
val itemView = menuView.getChildAt(i) as BottomNavigationItemView
itemView.setShiftingMode(false)
itemView.setChecked(false)
}
} catch (e: NoSuchFieldException) {
Log.e("FieldError", e.message)
} catch (e: IllegalAccessException) {
Log.e("IllegalAccessError", e.message)
}
}

前のセクションと変わったのが、itemView.setShiftingModeがfalseになったことです。

onMeasureの中でshowSmallLabelを呼び出すことも忘れずにお願いします。

先ほどのdisableSmallLabelを呼び出している場合はコメントアウトを忘れずに。

 

このように変更することで、下記のように表示が可能です。

f:id:rozkey59:20180212130352g:plain

 

まとめ

今回はBottomNavigationViewの使い方について紹介させて頂きました!

もちろんこれが正解というわけではないと思いますが、質問や間違っている部分などあればコメントをよろしくお願いします!

また、後でバッジを追加したい場合についても後ほど書こうかなと思います。

最後まで見ていただきありがとうございました。