妻とのこれまで。そしてこれから。 - 妻・夫を愛してるITエンジニア Advent Calendar 2016
※2016.12.18
後半慌てて書いたため、「終わりに」追記・修正しました。
はじめに
この記事は妻・夫を愛してるITエンジニア Advent Calendar 2016の17日目の記事です。
タイトル通り、妻とのこれまでとこれからの話をつらつらと。
略歴
私が大学三回生の頃だったので、もう10年経過しているのですねぇ。
中国の南通という街に短期留学したときに会ったのがきっかけです。
妻は日本語が話せず、私は英語が得意ではなかったので、今からするとどうしてこうなったと思わなくもないww
私が大学卒業後に 上海留学 -> 現地で就職 を経て一緒に暮らすようになり、仕事の都合で大連に引っ越して入籍。
3年ほど前に日本に帰国し、去年子どもが生まれて今は3人で暮らしている、といった流れです。
会話
語学留学の結果私が多少は中国語が話せることと、日本語を真面目に教えていない、ということもあって普段の会話は中国語です。
とはいえ私は話をするのが得意ではないので、基本的には妻が延々と話してくれるのをうんうんそうだね(全然理解できてない)ということが大半(苦笑)。
時々私が英語を勉強したい!と思って英語で話しかけたりしますが、大抵は無視されて中国語で帰ってきますorz
ま、いいんですけどね。
食事
文化が違うことで問題になりがちなのが食事だと思います。
が、私達の場合は嫌いな物が少ないからか、あまり問題にはなっていません。
定番の納豆も、むしろ妻のほうが積極的に買ってきたりして驚いた覚えがあります。
私の方は、香菜(パクチー)がちょっと苦手でしたが、真夏の朝から麻辣烫(雑な説明:辛いスープに具を入れて食べる、簡易の鍋みたいな食べ物)に別盛り香菜を食べるうちに慣れてきましたw
蘭州拉麺には香菜が入っていないと物足りないですねぇ。
唯一食べ物に絡んで困ったことといえば、妻が辛いもの好きで私はそうでない、ということでしょうか。
特に日本だとお店でピリ辛とか辛いとか書いていても食べてみるとそうでなかったりするので、そういうときは私が怒られます(解せぬ)。
家事
私は基本的に妻がやらないことをやる、というスタンスで、具体的に言えば朝のゴミ捨てとか食器洗いなどが私の作業となります。
ただ、明示的に決まっているわけでもないので妻がやってくれることもあれば、私が上記以外をやることもあります。
家事に限らず、なんとな~くそれなりにうまく回っている気がすることが、長く一緒にいられる理由なのかもと思ったりはします(向こうはガマンしてくれてるかもですが)。
あ、料理については妻が炒め物を始めとした中華料理しか作らないので、 日本料理的なものとかスパゲッティ(ほぼインスタントの調理と変わらないレベルですが)とかが食べたい、となると外食または私が作ることになります。
終わりに
皆さん惚気けている中で、惚気けているのだかそうでないのかわかりづらい内容になってしまいました…。
なんだかんだ10年一緒にいるので、なんとなくいつも当たり前にそばにいる、空気のような存在になっているような気もします。
しかし空気がなければ死んでしまうわけで。
もう少し恋人らしいあれこれもやってみても良いかな、と思いつつ、私としてはこんな生活がこれからも続いていってくれたらな、と思っています。
アドベントカレンダーのテーマであるITエンジニアにはほぼ理解がなく、私の勉強会参加とかで(尊重はしてくれますが)喧嘩したりはしますが😅
我爱你、老婆😁
【Unity】AndroidでNativeのGUIやら何やら追加したい - Androidその2 Advent Calendar 2016
はじめに
この記事はAndroidその2 Advent Calendar 2016の16日目の記事です。
UnityでAndroid用のアプリを作る場合に、Unity側で用意されている機能だけでは足りないなどの理由で、ネイティブのプラグインを作成したい場合があります。
今回はこの辺りの記事で書いたようなClassのファイルだけでなく、Layoutや画像などを追加する方法を調べてみました。
jarに画像を追加する
この記事と同じように、jarファイルをまず作成してプラグインとして利用する方法を試してみました。
例えば プロジェクトのルートフォルダ/app/src/main/assets に「images」というディレクトリを作成して、その中に入っているJPGファイルをjarファイルに追加したい場合は プロジェクトのルートフォルダ/app/src/build.gradle を下記のように作成します。
build.gradle
apply plugin: 'com.android.library' android { compileSdkVersion 25 buildToolsVersion "25.0.0" defaultConfig { minSdkVersion 19 targetSdkVersion 25 } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } sourceSets { main { resources{ srcDir "src/main/assets/images" resources.includes = ['**.jpg', '**.JPG', '**.png', '**.PNG'] } } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:25.0.1' compile files('libs/classes.jar') } task clearJar(type: Delete) { delete 'release/' + 'androidplugin01.jar' } task exportJar(type: Copy) { from('build/intermediates/bundles/release/') into('release/') include('classes.jar') rename('classes.jar', 'androidplugin01.jar') } exportJar.dependsOn(clearJar, build)
ビルド後に作成されるandroidplugin01.jarの容量が、画像分増えていれば成功で、
あとは「AssetManager assetManager = currentActivity.getResources().getAssets();」などでアクセスできます。
ただし、layout.xmlだのあれこれ追加したいと思い始めると、やっぱりこれは面倒な気がします…。
(これはこれで使い方によっては便利なのだとは思うのですが)
UnityでAndroidProjectをエクスポートする
Unity5から(多分)Androidのビルドとして、apkファイルを直接出力するだけでなく、AndroidProjectをエクスポートすることができるようになりました。
方法はBuild Settingsで2つの設定をするだけでできます。
- 「Build System」を「Gradle(New)」に変更する
- 「Export Project」にチェックを入れて「Export」ボタンを押す
するとフォルダ指定を求められるので、適当にフォルダを作成して、それを指定します。
なおUnityProjectと同じフォルダにすることはできません(UnityProject内のフォルダとかならOK)。
ネイティブの関数を呼ぶ
さて、出力されたAndroidProjectを開いてみると、 プロジェクトのルートフォルダ/src/main/java/jp/masanori/plugintest (jp/masanori/plugintestはパッケージ名)に、UnityPlayerActivity.javaがあります。
これはWindowsだと下記の場所にあるファイルと同じ内容です。
C:\Program Files\Unity\Editor\Data\PlaybackEngines\AndroidPlayer\Source\com\unity3d\player UnityPlayerActivity.java
プラグインの関数を呼ぶときと同じく、このUnityPlayerActivityをUnity側から呼んでやればアクセスできそうです。
Unity
ということでまずはUnity側。
PluginCtrl.cs
using UnityEngine; namespace Assets.Scripts { public class PluginCtrl : MonoBehaviour { private readonly string PluginClassPath = "jp.masanori.plugintest.UnityPlayerActivity"; public void RequestFilePathList() { using (var androidJavaClass = new AndroidJavaClass(PluginClassPath)) { androidJavaClass.CallStatic("requestFilePathList"); } } } }
このような関数を、例えばボタンが押されたなどのタイミングで呼ぶことにします。
MainCtrl.cs
using UnityEngine; using UnityEngine.UI; namespace Assets.Scripts { public class MainCtrl : MonoBehaviour { public GameObject PluginCtrlObject; public Button ImageButton; private PluginCtrl pluginCtrl; private void Start () { pluginCtrl = PluginCtrlObject.GetComponent(); ImageButton.onClick.AddListener(OnImageButtonClicked); } private void OnImageButtonClicked() { pluginCtrl.RequestFilePathList(); } } }
AndroidManifest.xml
< ?xml version="1.0" encoding="utf-8"? > < manifest xmlns:android="http://schemas.android.com/apk/res/android" package="jp.masanori.plugintest" xmlns:tools="http://schemas.android.com/tools" android:installLocation="preferExternal" android:versionCode="1" android:versionName="1.0" > < permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/ > < permission android:name="android.permission.READ_EXTERNAL_STORAGE" / > < supports-screens android:smallScreens="true" android:normalScreens="true" android:largeScreens="true" android:xlargeScreens="true" android:anyDensity="true"/ > < application android:theme="@style/UnityThemeSelector" android:icon="@drawable/app_icon" android:label="@string/app_name" android:debuggable="true" > < activity android:name=".UnityPlayerActivity" android:label="@string/app_name" > < intent-filter > < action android:name="android.intent.action.MAIN" / > < category android:name="android.intent.category.LAUNCHER" / > < /intent-filter> < meta-data android:name="unityplayer.UnityActivity" android:value="true" / > < /activity > < /application > < /manifest >
C:\Program Files\Unity\Editor\Data\PlaybackEngines\AndroidPlayer\Apk にあるAndroidManifest.xmlをベースにしていますが、UnityProject側に入れる必要は無いかもしれません。
Android
AndroidProjectとして出力したあと、UnityPlayerActivity.javaを編集します。
UnityPlayerActivity.java
package jp.masanori.plugintest; import com.unity3d.player.UnityPlayer; import android.app.Activity; import android.content.res.Configuration; import android.graphics.PixelFormat; import android.os.Bundle; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.Window; import android.view.WindowManager; public class UnityPlayerActivity extends Activity { protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code private static void requestFilePathList(){ // TODO: 画像ファイルへのアクセス. } // Setup activity layout @Override protected void onCreate (Bundle savedInstanceState) { requestWindowFeature(Window.FEATURE_NO_TITLE); super.onCreate(savedInstanceState); getWindow().setFormat(PixelFormat.RGBX_8888); // <--- This makes xperia play happy mUnityPlayer = new UnityPlayer(this); setContentView(mUnityPlayer); mUnityPlayer.requestFocus(); } ~省略~
これでUnity側でボタンが押されたときに「requestFilePathList()」が呼ばれるようになるので、あとは通常のAndroidアプリと同じようにassetsフォルダの画像にアクセスすればOKです。
もし画像をUnity側から操作したい場合は、Filesフォルダなどに画像をコピーする必要があるかもしれません。
DataBindingを使う
エクスポート後のProjectは通常のAndroidアプリのProjectとほぼ同じです。
なので、DataBindingを使うこともできます。
まずはLayoutを作成します。
native_button_layout.xml
< ?xml version="1.0" encoding="utf-8"? > < layout xmlns:android="http://schemas.android.com/apk/res/android" > < RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" > < Button android:text="@string/native_button_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_alignParentStart="true" android:layout_marginStart="65dp" android:layout_marginTop="82dp" android:id="@+id/native_button" / > < /RelativeLayout > < /layout >
DataBindingを有効にします。
build.gradle
// GENERATED BY UNITY. REMOVE THIS COMMENT TO PREVENT OVERWRITING WHEN EXPORTING AGAIN buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.2.2' } } allprojects { repositories { flatDir { dirs 'libs' } } } apply plugin: 'com.android.application' dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) } android { compileSdkVersion 25 buildToolsVersion '25.0.0' defaultConfig { targetSdkVersion 25 } lintOptions { abortOnError false } signingConfigs { release { storeFile file('C:/Users/masanori/.android/debug.keystore') storePassword 'android' keyAlias 'androiddebugkey' keyPassword 'android' } } buildTypes { debug { jniDebuggable true } release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt' signingConfig signingConfigs.release } } dataBinding { enabled = true } }
UnityのViewに追加します。
~省略~ import jp.masanori.plugintest.databinding.NativeButtonLayoutBinding; public class UnityPlayerActivity extends Activity { protected UnityPlayer mUnityPlayer; // don't change the name of this variable; referenced from native code private static void requestFilePathList(){ // TODO: 画像ファイルへのアクセス. } // Setup activity layout @Override protected void onCreate (Bundle savedInstanceState) { requestWindowFeature(Window.FEATURE_NO_TITLE); super.onCreate(savedInstanceState); getWindow().setFormat(PixelFormat.RGBX_8888); // <--- This makes xperia play happy // DataBindingを取得してmUnityPlayerに追加. NativeButtonLayoutBinding binding = DataBindingUtil.setContentView(this, R.layout.native_button_layout); mUnityPlayer.addView(binding.getRoot()); mUnityPlayer = new UnityPlayer(this); setContentView(mUnityPlayer); mUnityPlayer.requestFocus(); }
ポイントとしてはDataBindingはmUnityPlayerに追加する、ということでしょうか。
「setContentView(mUnityPlayer);」の後にDataBindingの処理を行うと、Unity側の画面が消えてネイティブ側の画面のみが表示され、悲しい気持ちになります。
また、下記はプラグイン作成時の注意ではありますが、今回の場合も気をつけた方が良いかもしれません。
おわりに
iOSのネイティブプラグインは以前からUnityのビルド後に追加する形であったため、Androidでも同じようにできる、というのは便利ですね。
ただ多くの場合jarファイルのように一つにファイルがまとまることがないため、複数プロジェクトで使いまわしする場合などは不便かも?
そもそもUnity側で解決できるならそちらでやったほうが…。
とまぁ微妙なところもありますが、適材適所、便利に問題に対応していくのが良いかと思います。
参考
RiderでUnityアプリ開発するときに詰まったとことか - JetBrains Advent Calendar 2016
はじめに
この記事はJetBrains Advent Calendar 2016の11日目の記事です。
まだEarlyAccessProgramの段階ではあるものの、C#用のIDEであるRiderが一般ユーザーでも使用可能になりました。
早速UnityのScriptを書くのに使ってみたところ、いくつか引っかかったところがあったので書き残しておきます。
※この記事は2016.12.11時点のもの、かつRiderは正式版ではないため、今後の改修で不要になるかもしれません。
Monoのインストール
Windowsの場合だと(おそらく).Net Frameworkが使われるため必要ないかもですが、Macの場合はMonoをインストールする必要があるようです。
(最初開いた瞬間にエラーまみれでクラクラきた思い出…)
MonoはUnityで使うMono Developにも含まれていると思いますので、そちらを使うのも良さそうです。 が、Unityを複数バージョンインストールする場合などはややこしいので、特に理由がなければ別途インストールしてあげる方が良いかと。
プラグインのインストール
Unity用のプラグインが作成されています。
https://github.com/JetBrains/Unity3dRider
使い方はUnityプロジェクトのAssets\Plugins\Editor\JetBrainsに設置するだけ。
これが無いとどうなるか、というと、最初にMono Developなどで生成したソリューションファイルを開いたときは問題なくても、 Unity側で何かを変更した場合などにエラーまみれになったりします。
次のソリューションファイル生成と合わせて、面倒ではあるもののRiderを使うのであれば現状プラグインのインストールは必須と言えそうです。
ソリューションファイルの生成
Mono Develop、Visual Studioを使う場合、Unityエディタ上でスクリプトファイルをダブルクリックするとソリューションファイル(.sln)などが存在しなければ自動で生成した上でそのプロジェクトを開きます。
Riderの場合はその方法だと生成されず、ソリューションが見つからないとエラーになります。
ではどうするか。
上記のUnity用プラグインをインストールすると、メニューのAssets以下に「Open C# Project in Rider」という項目が追加されるので、これをクリックすることでソリューションファイルなどを生成できます。
ソリューションファイルさえ生成しておけば、あとはスクリプトファイルのダブルクリックでもプロジェクトを開くことができるようになります。
Unity Support Plugin
定着しているからなのか、誰も気にしていないからなのかは分かりませんが、Unity用のプラグインはもう一つあります(Resharper用に作成されたもののようです)。
Windowsの場合はFile/Settings/Pluginsの「Install JetBrains plugin」からUnityで検索すると以下が見つかると思います。
https://resharper-plugins.jetbrains.com/packages/JetBrains.Unity/
このプラグインをインストールすると、StartやUpdateといった、Unity標準のイベント関数に対して、 どこからも呼ばれてないよとWarningがでることが防げます。
Sharderの編集
Sharderも編集自体は可能です。
が、カラースキーマは(デフォルトでは)用意されていないようなので、メモ帳などで開いた場合と変わらない感じです。
自作してみようかと思ったのですが方法がわからず…。
ここは今後解決できたら投稿したいと思います。
命名規則
デフォルトの命名規則
命名規則については、Unityエディタからクラスを作った場合は特に以下のような点でWarningが出ます。
namespace: (ファイルがAssets/Scriptsにある場合) namespace Assets.Scripts を指定する。
namespace Assets.Scripts { public class MainCtrl : MonoBehaviour { } }
※2017.04.24更新
2017年4月現在では、
プラグインを入れているとWarningが出なくなるようです。
public変数: Pascal方式で書く
public string PublicText;
private変数: _(アンダースコア)始まりのCamelCase
private string _privateText;
enum: Pascal方式で書く
private enum TestEnum{ Num0 = 0 , Num1 , Num2 }
命名規則の変更
namespaceはともかく、変数の命名規則は何がベースになっているのかが少し気になりました。
(特にprefixとしてアンダースコアを付けるprivate変数)
Microsoftの命名規則だとprefixは禁止されていますね。
- General Naming Conventions - Microsoft Developer Network.aspx)
- Naming Guidelines - Microsoft Developer Network.aspx)
ということでprivate変数を、prefix無しのCamelCaseに変更してみます。
まずprefix無しのprivate変数を用意して、左に出てくる電球をクリックします。
Inspection: “Inconsistent Naming” -> Change naming rule ‘Instance fields (private)’ をクリックします。
Rule Settingsウインドウが開きます(しばらく待っても表示されない場合は、ウインドウ切り替えて開いてないか確認してみてください)。
「Name Prefix」の「_」を削除して「Save」ボタンをクリックします。
※2017.04.24更新
2017年4月現在のバージョンでは、
右クリックからではなく、
Preferences > Editor > Code Style > C#
にある「Naming」タブから変更ができるようになっています。
その他
スクリプトファイルの追加
ここまでの設定が完了していたら、Unityから追加してもRiderから追加しても問題なく反映されると思います。
ただMonoBehaviourを継承するクラスが必要ならUnityから追加した方が良く、
interfaceなどクラス以外を追加したい場合はRiderから行った方が良いと思います(そもそもUnityから追加できるのがクラスだけなので)。
ショートカット
これはMacの話ですが、ショートカットをVisual Studioなどに設定すると、置換のショートカットが「Command + H」に設定されます。
が、実際にやってみるとウインドウが最小化されますorz (OS側のショートカットと重なっているため)
そのため、設定のKeymapから適当な組み合わせに変更してあげる必要があります。
おわりに
まだ開発版ということで、複数の変数にエラーがあるとそれらを修正しても画面が赤いまま、といったエラーは見受けられるものの、かなり良い感じで使えています。
デバッグの機能はまだ難しいようですが、スクリプトの編集という機能においては、入力補完がバリバリ効いてかなり便利です。
普段IntelliJ IDEAやAndroid Studioなどを使っている方は、ぜひ触ってみると良いのではないかと思います。
参考
子育てとエンジニア -子育てエンジニア Advent Calendar 2016
はじめに
この記事は子育てエンジニア Advent Calendar 2016の6日目の記事です。
現在1歳4ヶ月の息子が生まれてから変わったことをつらつらと書いてみることにします。
変わったこと
一番大きいのは時間でしょうか。
どうしても子供が起きている間は他のことに時間が割きづらいので、プライベートで開発したりするのは寝静まったあとにしています。 もしくは会社帰りにカフェなどに寄るとか。
フラッと立ち寄れるコワーキングスペースがあると良いのですが、ここ和歌山ではそんな便利なものはなく…。
勉強会も同じで、妻に子供を任せて割りと自由に参加させてもらってはいますが(妻に感謝)、
やっぱりもくもく会とか頻度が高くなってきたりすると参加を取りやめたり、ということはあります。
ただ、今は直接勉強会に参加できなかったとしても、Twitterなどに情報を流してくれる方がいたり、
VRを中心としてインターネットを通して参加できるイベントが増えてきていてありがたい限りです。
来年ですが、Swift TweetsはTwitterを使って行う勉強会、ということで、どんなことになるのかとても楽しみにしています。
(その前に今のSwiftを勉強しておかなきゃですが)
直接会場に足を運んで、時には発表したり、周りの誰かとお話する。
それが素晴らしいことには間違いありませんが、それ以外にも参加できる方法が増えていってくれると良いな、と思います。
グッズ
最近買ってちょっと便利なものを2つほど。
静音マウス ま、マウスのクリック音で起きるということはないのですが、やっぱりカチカチいうと気になるので…。
Chromecast 購入後わりとすぐに4K対応版が出てビミョーな気持ちにはなりましたが、初期設定の他はWi-Fi環境で接続している端末なら何もインストールなしで操作できてしまうので結構良いです。
普段はYoutubeばかりですが、PlayStoreでアンパンマンの映画もあるようなので購入しても良いかも。
ちなみに先日買ったMadMaxはまだ観られていません。
終わりに
あまりポジティブな内容にはならなかった気もしますが、この一年で(おそらく)失われているものもたくさんある一方で、得たものがとても多いと感じています。
まぁべったりひっついてくれるのも今だけで、その内「くせーよオヤジ~」とか言って離れていくんだろうなぁ。
ちなみに将来本当に息子がそんなことを言った場合は、地獄のゆりかごを見舞う予定です(๑•̀ㅂ•́)و✧
それはともかく、押し入れにLeapMotionやらArduinoやら眠っているので、息子がもう少し大きくなったら一緒にプログラミングして何か作ってみるのも面白いかも、と思ったりしています(やりたがったらの話ですが)。
とりとめない内容になりましたが、今日も我が家は平和である、ということでした。
明日はseikoudoku2000さんの予定です。
よろしくお願いいたしますm( )m
【Kotlin】【Android】RxJavaとUniRxの対応表を作ってみたい 1
はじめに
この記事はRxJava Advent Calendar 2016の2日目に勢いのみでつっこもうとしている記事です。
それはさておき。
先月参加したKansai.kt #2でReactiveProgrammingとRxJavaの話を聞いて以降、本を読みつつコードを書いてみたりしています。
その中で、(お仕事ではUnityを使うことが多く、UniRxも導入しているため)UniRxと同じような処理から書いてみるととっつきやすいんじゃね?と思い立ったため、思いつく順番でそれぞれのコードを書いてみたいと思います。
なおRxJavaの方はAndroid環境で実行しており、言語はKotlinを使います。
環境
- Unity 5.5
IntelliJ IDEA 2016.3
UniRx 5.5
- Kotlin v1.0.5-release-IJ2016.3-2
準備
UniRx
UniRxをインポートしたUnityプロジェクトを作成します。
RxJava
後述しますが、RxJava、RxAndroid、RxBindingを使います(ついでにDataBindingも有効にしています)。
build.gradle
apply plugin: 'com.android.application' apply plugin: 'kotlin-android' android { compileSdkVersion 25 buildToolsVersion "25.0.0" defaultConfig { applicationId "jp.masanori.kandroidtest" minSdkVersion 19 targetSdkVersion 25 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } sourceSets { main.java.srcDirs += 'src/main/kotlin' } dataBinding { enabled = true } packagingOptions { exclude 'META-INF/rxjava.properties' } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:25.0.1' compile 'io.reactivex.rxjava2:rxandroid:2.0.1' compile 'com.jakewharton.rxbinding:rxbinding-appcompat-v7-kotlin:1.0.0' compile 'io.reactivex.rxjava2:rxjava:2.0.1' testCompile 'junit:junit:4.12' compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" kapt 'com.android.databinding:compiler:1.0-rc5' } repositories { mavenCentral() } kapt { generateStubs = true }
ボタンクリックのイベント
なんとなくGUIから始めます。
ボタンをクリックしたときのイベントを取得してみます。
UniRx
using UnityEngine; using UnityEngine.UI; using UniRx; public class MainCtrl: MonoBehaviour{ public Button startButton; private void Start(){ // ボタンにクリックイベントを追加する. startButton.onClick.AsObservable() .Subscribe(_ => Debug.Log("クリックされました")); } }
RxJava
RxJavaでは上記のようにボタンクリックを取得することはできないようです。
まぁAndroid用に作られたものではないため、当然といえば当然なのですが。。。
ではどうするかといいますと、RxBindingを使用します。
準備のところで書いたように、「compile 'com.jakewharton.rxbinding:rxbinding-appcompat-v7-kotlin:1.0.0'」を追加します。
なおJavaで書く場合は「-kotlin」の部分を削除してください。
ここでRxJava2を使っていて、「compile ~」だけを追加した場合に「com.android.builder.packaging.DuplicateFileException」が発生します。
どうもRxJava1系と2系が競合してしまうらしく、下記を追加する必要があります。
~省略~ android{ ~省略~ packagingOptions { exclude 'META-INF/rxjava.properties' } } ~省略~
あとは下記のようにボタンのクリックイベントを取得できるようになります。
import android.databinding.DataBindingUtil import android.support.v7.app.AppCompatActivity import android.os.Bundle import android.util.Log import com.jakewharton.rxbinding.view.RxView import io.reactivex.disposables.CompositeDisposable import jp.masanori.kandroidtest.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { lateinit private var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) binding = DataBindingUtil.setContentView(this, R.layout.activity_main) // ボタンにクリックイベントを追加する. RxView.clicks(binding.startButton).subscribe { Log.d("RxTest", "クリックされました") } } }
一定時間ごとに処理を行う
次は一定時間ごとに処理を行ってみます。
UniRx
private void Start () { // 500ミリ秒ごとにログ出力. var intervalObservable = Observable.Interval(System.TimeSpan.FromMilliseconds(500d)) .Subscribe(time => Debug.Log("経過: " + time)); // 画面遷移などのタイミングでDisposeを実行して接続を解除する. // intervalObservable.Dispose(); }
RxJava
import android.support.v7.app.AppCompatActivity import android.os.Bundle import android.util.Log import com.jakewharton.rxbinding.view.RxView import rx.Observable import rx.schedulers.Schedulers import java.util.concurrent.TimeUnit class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 500ミリ秒ごとにログ出力. val intervalObservable = Observable.interval(500, TimeUnit.MILLISECONDS, Schedulers.io()) .subscribe { Log.d("RxTest", "経過:" + it) } // 画面遷移などのタイミングで接続を解除する. // intervalObservable.unsubscribe() } }
RxJavaの方ではIDisposableが継承されていないようで、DisposeはできずUnsubscribeするのみとなっています。
処理の書き方自体はかなり近い感じですね。
一定時間後に処理を行う
一定時間が経過したら処理を行います。
UniRx
private void Start () { var timerObservable = Observable.Timer(System.TimeSpan.FromSeconds(3d)) .Subscribe(time => Debug.Log("3秒経過しました")); //timerObservable.Dispose(); }
RxJava
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val timerObservable = Observable.timer(3, TimeUnit.SECONDS) .subscribe { Log.d("RxTest", "3秒経過しました") } // timerObservable.unsubscribe() }
おわりに
細かい違いはともかく、同じものを元にしているだけに大部分の書き方や考え方は共通化されていて助かります。
とりあえずしばらくはこんな感じでバラバラと挙げていって、量が溜まってきたら別途整理、といったところでしょうか。
参考
RxJava
UniRx
RxAndroid
RxBinding
Kansai.kt #2で発表してきました
はじめに
11/26に行われたKansai.kt #2で、ラムダ式について発表してきました。
資料
テーマについて
このテーマにした理由は、そもそも私がKotlinに触るきっかけになったことが、 Androidで使うことができること、前回のテーマとしたNull安全、そしてラムダ式がAndroidでも使えるということでした。
Null安全の発表については(一応)挑戦したため、もう一つのラムダ式に挑戦した、ということです。
内容について
これまでなんとなくコードを書いたことはあったものの、どういう関数がラムダ式として書けるのか、 そもそもラムダ式ってなんぞやといったところはわかっていなかったため、そこに少しでも触れられたことは良かったと思います。
ただ、20~30分の発表時間を結構余らせてしまったため、もっと色々なところに突っ込んだり、 もしくはせっかく以前Scala Kansai Summitに参加させてもらったので、関数型プログラミング的なアプローチをしてみる、といったところに挑戦しても良かったのかもしれません。
これは発表する・しないはともかく今後挑戦してみたいですね。
他の方の発表について
次期バージョンであるKotlin1.1のお話、Android開発におけるJavaからKotlinにコンバートした後にやるべき処理、IntelliJ IDEAのPostfixコード補完のお話と、どれも興味深く自分も取り入れていきたいな、と思いながら聞いていました。
そして結果的に今回のメインになったと言っても過言ではないReactiveProgrammingとRxJavaのお話。 AndroidでRxJavaに触れたり、UnityでUniRxを利用していたりと、Rx自体には普段から触れているものの、概念・考え方というところではなんとなくのところが多すぎる気はしていたので、基礎的な部分からガッツリお話していただけたのはありがたかったです。
CyberMondayでセール中だったこともあり本も購入してしまいましたので、こちらも進めていきたいです。
Reactive Programming with RxJava - O'Reilly Media
おわりに
Kotlinに限ったことではありませんが、Androidなどのアプリケーションを作る手法だけでなく、言語自体に対して学ぶ、というのもとても楽しく、また有意義だと思います。
次回は内容が変わるらしいというお話もあるので?どうなるかは分かりませんが、 発表の機会があればより内部の突っ込んだところまでいけるように頑張ります。
スポンサーとなっていただいたはてな様、主催者の皆様、そして参加者の皆様、本当にありがとうございましたm( )m
Kotlin Koansやってみたメモ Conventions編
- はじめに
- Conventions - In range
- Conventions - Range to
- Conventions - For loop
- Conventions - Operators overloading
- Conventions - Invoke
はじめに
前回の続きです。
今回はConventionsにトライしたときのメモです。
ここではまずComparisonで下記のようなDataクラスを作り、これをベースに問題を解いていきます。
data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int) : Comparable{ override fun compareTo(other: MyDate) = when { year != other.year -> year - other.year month != other.month -> month - other.month else -> dayOfMonth - other.dayOfMonth } }
このクラスではComparableを継承し、compareToで日付の比較ができるようにしています。
Conventions - In range
KotlinではOperatorを オーバーロードすることができます。
例えば下記のTestDataクラス同士は、「+」または「plus」で足し合わせることができます。
data class TestData(var num: Int, var text: String){ operator fun plus(addData: TestData): TestData = TestData(this.num + addData.num, this.text + addData.text) } @Throws(IOException::class) override fun start(primaryStage: Stage) { var originaldata = TestData(0, "test") var adddata = TestData(1, "2") // どちらもOK originaldata = originaldata.plus(adddata) originaldata = originaldata + adddata }
で、ここではある日付(MyDate)が指定の期間内に含まれるかをチェックするため、containsを持つDataRangeクラスを作成します。
class DateRange(val start: MyDate, val endInclusive: MyDate){ operator fun contains(item: MyDate) = item >= start && item <= endInclusive }
Kotlin Koansではこのクラスを「in」を使って利用しています。
@Throws(IOException::class) override fun start(primaryStage: Stage) { val dateTarget = MyDate(2016, 10, 9) val dateFirst = MyDate(2016, 9, 30) val dateLast = MyDate(2017, 11, 1) // Trueが返る val result = checkInRange(dateTarget, dateFirst, dateLast) } fun checkInRange(date: MyDate, first: MyDate, last: MyDate): Boolean { return date in DateRange(first, last) }
他にも下記のように呼び出すことも可能です。
@Throws(IOException::class) override fun start(primaryStage: Stage) { val dateTarget = MyDate(2016, 10, 9) val dateFirst = MyDate(2016, 9, 30) val dateLast = MyDate(2017, 11, 1) val dataRange = DateRange(dateFirst, dateLast) // Trueが返る var result = dataRange.contains(dateTarget) }
なお、containsで日付を比較するところでは、先に実装しているcompareToが呼ばれます。
Conventions - Range to
MyDateクラスに日付の範囲を返すrangeToを追加します。
operator fun MyDate.rangeTo(other: MyDate) = DateRange(this, other) class DateRange(override var start: MyDate, override val endInclusive: MyDate): ClosedRangefun checkInRange(date: MyDate, first: MyDate, last: MyDate): Boolean { return date in first..last }
ある日付(date)が日付の範囲(firstからlast)に含まれるかを確認している「date in first..last」は、下記のように書くことも可能です。
val dateTarget = MyDate(2016, 10, 9) val dateFirst = MyDate(2016, 9, 30) val dateLast = MyDate(2017, 11, 1) // 日付の範囲(DataRange)を作る val dateRange = dateFirst.rangeTo(dateLast) // Trueが返る val res = dateRange.contains(dateTarget)
ここで気になったのがDateRangeクラス。
引数がOverrideされていて、Kotlinてこんなこともできるのかぁ(こなみかん)という感じだったのですが、これはClosedRangeクラスを継承しているためのようです。
サブクラスで引数の値を変更するにはOverrideが必要、という。
Conventions - For loop
For-loop rangeについて。
ここではDateUtil.ktも使って解いていくのですが、面倒なので一つのクラスにまとめて書いていたらちょっとハマりましたorz
data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int) : Comparable{ override fun compareTo(otherItem: MyDate) = when { year != otherItem.year -> year - otherItem.year month != otherItem.month -> month - otherItem.month else -> dayOfMonth - otherItem.dayOfMonth } } operator fun MyDate.rangeTo(other: MyDate) = DateRange(this, other) inner class DateRange(val start: MyDate, val end: MyDate): Iterable { override fun iterator(): Iterator = DateIterator(this) } fun MyDate.nextDay() = addTimeIntervals(TimeInterval.DAY, 1) enum class TimeInterval { DAY, WEEK, YEAR } fun MyDate.addTimeIntervals(timeInterval: TimeInterval, number: Int): MyDate { val c = Calendar.getInstance() c.set(year + if (timeInterval == TimeInterval.YEAR) number else 0, month, dayOfMonth) var timeInMillis = c.getTimeInMillis() val millisecondsInADay = 24 * 60 * 60 * 1000L timeInMillis += number * when (timeInterval) { TimeInterval.DAY -> millisecondsInADay TimeInterval.WEEK -> 7 * millisecondsInADay TimeInterval.YEAR -> 0L } val result = Calendar.getInstance() result.setTimeInMillis(timeInMillis) return MyDate(result.get(Calendar.YEAR), result.get(Calendar.MONTH), result.get(Calendar.DATE)) } inner class DateIterator(val dateRange:DateRange) : Iterator { var current: MyDate = dateRange.start override fun next(): MyDate { val result = current current = current.nextDay() return result } override fun hasNext(): Boolean = current <= dateRange.end } fun iterateOverDateRange(firstDate: MyDate, secondDate: MyDate, handler: (MyDate) -> Unit) { for (date in firstDate..secondDate) { handler(date) } }
- DateRange、DateIteratorクラスは「inner」をつけて内部クラスにする必要があり、ただネストしただけでは外側にある「MyDate.nextDay()」などにアクセスできません。
Iterable、Iterator
この問題では、for文である期間(firstDate)からある期間(SecondDate)までの日をhandlerに渡す、という処理を作ります。
で、まず「date in firstDate..secondDate」を実現するために「Iterable
Iteratorでは渡された日付が「secondDate」より前かを確認する「hasNext」と、次の日を返す「next」を実装しています。
Conventions - Operators overloading
日付の足し算と、それを繰り返して処理するためのクラスを作ります。
で、そうそうにギブアップしましてorz、答えを見てみました。
data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int) enum class TimeInterval { DAY, WEEK, YEAR } operator fun MyDate.plus(timeInterval: TimeInterval): MyDate = addTimeIntervals(timeInterval, 1) fun task1(today: MyDate): MyDate { return today + TimeInterval.YEAR + TimeInterval.WEEK } class RepeatedTimeInterval(val timeInterval: TimeInterval, val number: Int) operator fun TimeInterval.times(number: Int) = RepeatedTimeInterval(this, number) operator fun MyDate.plus(timeIntervals: RepeatedTimeInterval) = addTimeIntervals(timeIntervals.timeInterval, timeIntervals.number) fun task2(today: MyDate): MyDate { return today + TimeInterval.YEAR * 2 + TimeInterval.WEEK * 3 + TimeInterval.DAY * 5 } fun MyDate.addTimeIntervals(timeInterval: TimeInterval, number: Int): MyDate { val c = Calendar.getInstance() c.set(year + if (timeInterval == TimeInterval.YEAR) number else 0, month, dayOfMonth) var timeInMillis = c.getTimeInMillis() val millisecondsInADay = 24 * 60 * 60 * 1000L timeInMillis += number * when (timeInterval) { TimeInterval.DAY -> millisecondsInADay TimeInterval.WEEK -> 7 * millisecondsInADay TimeInterval.YEAR -> 0L } val result = Calendar.getInstance() result.setTimeInMillis(timeInMillis) return MyDate(result.get(Calendar.YEAR), result.get(Calendar.MONTH), result.get(Calendar.DATE)) }
とりあえずtask1の方は問題なさそうですね。MyDateに対して「+」を使ったときに、関数オブジェクト「addTimeIntervals」が呼ばれるようにする、と。
ちなみに、関数オブジェクトの書き方が以前は「operator fun MyDate.plus(timeInterval: TimeInterval): MyDate = ::addTimeIntervals」のようになっていましたが、仕様変更があったのですね(今更感)。
問題はtask2の方。
MyDateに対して「* 数値」が行われた場合に、「+」をその数値文繰り返す、という内容となります。
で、この繰り返しを担う関数が「times」です。
「TimeInterval.times(number: Int)」で「RepeatedTimeInterval」というクラスが返されるようにしています。
で、このままだと「+」の型が違う(´;ω;`)とエラーになるので、「MyDate.plus(timeIntervals: RepeatedTimeInterval)」で対応、という流れです。
これ、今もなお答え見ないと回答できる気がしない...orz
Conventions - Destructuring declarations
Data クラスは分割して個別に扱うことができます、という内容でした。
data class MyDate(val year: Int, val month: Int, val dayOfMonth: Int) @Throws(IOException::class) override fun start(primaryStage: Stage) { val date = MyDate1(2016, 11, 12) val (year, month, dayOfMonth) = date println(year.toString()) println(month.toString()) println(dayOfMonth.toString()) }
こんな感じ。
Conventions - Invoke
最後はInvokeの問題。
class Invokable { public var numberOfInvocations: Int = 0 private set operator public fun invoke(): Invokable { numberOfInvocations++ return this } } fun invokeTwice(invokable: Invokable) = invokable()()
例えば下記のようにすると「invoke()」が2回呼ばれるので「numberOfInvocations」の値は2になります。
@Throws(IOException::class) override fun start(primaryStage: Stage) { // 初期化時はnumberOfInvocations = 0 var invokable = Invokable() // invoke()が二回呼ばれるのでnumberOfInvocations = 2 invokable()() }
このInvokeですが、ググってみるとカリー化と呼ばれる複数の引数を持つ一つの関数を、一つの引数を持つ複数の関数に置き換える際に登場しているようです。
これについてはこの辺りが参考になりそうです。
おわりに
ConventionsではKotlin特有のテクニック、というよりはKotlinを使って「+」などのオペレーターを自分で作ることで言語仕様に触れる、という内容だった気がします。
正直まだ回答を見ながらふむふむそういう風に書くのか、と思っているだけの状態ではあるので、時間を置いてもう少し理解できるようになったら追記するか別に訂正記事を書こうかと思います。
本当は11/26のKansai.ktまでに、次のCollectionsまで行きたかったところですが、このペースだと資料作る時間もなくなりそうなので一旦ストップしてそちらの準備を進めたいと思います。
参考
- Kotlin Koans - Try Kotlin
- Reference - Kotlin Programming Language
- Kotlinスタートブック──新しいAndroidプログラミング──
- WEB+DB PRESS Vol.94
Operator overloading
- Operator overloading - Kotlin Programming Language
- 【Kotlin】 OperatorとInfixでCustom operatorを作る - Qiita