【Android】Kotlin/KodeinではじめてのDI
Kotlin/Kodeinを用いてはじめてのDIをしてみましょう
初めてDIに触れる人を対象読者としています。Kodeinの詳細な実装については解説しておりません。
DIとはなにか、なにがありがたいのか
DIとはDipendency Injectionの略で、日本語だと依存性注入と言われます。あるクラスAから依存するクラスBがあるとして、その依存関係を外部から指定してあげるものです。
↓Non DIの場合
↓DIの場合
クラスAでは依存先の抽象だけを指定し具象を外部から注入することで、コンポーネント間の依存度が下がり、疎結合なプログラムを構築することができます。
具体例を挙げます。web apiを叩きデータをダウンロードしてくるアプリがある場合、デバッグ中はハードコードした値を使いたいとか、デバッグ用サーバにつなぎたいということがあると思います。
そんなときには外部のほうでbuild flavorを見るなどして注入する依存を切り替えます。
ソースコードにデバッグビルド・リリースビルドで切り替える処理が散るのを防ぐことができたり、テストに対応しやすくできたりします。
Kotlin/Kodeinで最速DIする
それでは、Kotlin/Kodeinで最速でDIしてみましょう。Kodeinの使い方の解説を主眼とします。
環境
Android Studio 3.0 Canary 2 Kotlin 1.1.2-4 Kodein 4.0.0
Kodein導入
dependencies { ~ compile 'com.github.salomonbrys.kodein:kodein:4.0.0' compile 'com.github.salomonbrys.kodein:kodein-android:4.0.0' }
Stringを注入してみる
例えば"Hello, Kodein!“とログに吐く処理があるとして、この文字列を外部から注入するようにしてみます。
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) Log.d("---", "Hello, Kodien!") } }
さてこれまで外部外部と言ってきましたが、今回であれば「MainActivity以外」ですね。ということでApplicationクラスのサブクラスにしてみます。ライフサイクル的に先行する部分にしておきます。
class MyApplication : Application(), KodeinAware { override val kodein = Kodein { bind<String>() with instance("Hello, Kodein!") } }
Kodeinが登場しました。
Applicationクラスを継承しつつKodeinAwareを実装し、val kodeinをオーバーライド、セットアップします。ここで文字列をバインド(注入するインスタンスとして宣言)します。
ではバインドした"Hello, Kodien!“を取り出す実装をMainActivityに施します。
class MainActivity : KodeinAppCompatActivity() { val greeting: String by instance() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) Log.d("---", greeting) } }
まず継承元をKodeinAppCompatActivityもしくはKodeinActivityにします。これはActivityでKodeinを扱いやすくするラッパーです。
次に文字列の注入ですが、val greeting: String by instance()
がそれにあたります。これでMyApplicationにてバインドした文字列がgreetingへ代入されます。
実行してみると、ログに"Hello, Kodein!“が正しく表示されることが確認できると思います。また"Hello, Kodein!"を別の文字に変えてもログが連動して変わると思います。
Retrofit.Serviceを注入してみる
もう少し実用的な、build flavorをみて依存を切り替える例を挙げます。 Moduleという仕組みを使うことで、バインドをまとめることができます。
interface GithubService { @GET("/repositories") fun get(): Observable<List<Repository>> }
class TestGithubService : GithubService { override fun get(): Observable<List<Repository>> = Observable.just( listOf(Repository("Repo-1"), Repository("Repo-2"), Repository("Repo-3")) ) } val debugModule = Kodein.Module { bind<GithubService>() with instance(TestGithubService()) }
val releaseModule = Kodein.Module { bind<GithubService>() with instance(Retrofit.Builder() .baseUrl("https://api.github.com/") .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build() .create(GithubService::class.java)) }
class MyApplication : Application(), KodeinAware { override val kodein by Kodein.lazy { when (BuildConfig.FLAVOR) { "debug" -> import(debugModule) "release" -> import(releaseModule) } } }
class MainActivity : KodeinAppCompatActivity() { val githubService: GithubService by instance() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) githubService.get() .map { repository -> repository.map { (name) -> name } } .subscribeOn(Schedulers.newThread()) .observeOn(AndroidSchedulers.mainThread()) .subscribe { strings -> (findViewById(R.id.list) as ListView).adapter = ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, strings) } } }
以上です
Kotlin/Kodeinを用いて簡単にDIの概要解説とサンプルの紹介をしました。
Kodeinの実装については公式のドキュメントが充実しているので、参考にしてみてください。
ありがとうございました。
参考
【GAE/Go】 'ImportError: cannot import name goroots' の対処
こんにちは。
GAE/Go開発している最中にふとappengine sdkのアップデートをしました。
$ gcloud components update
これでappengine sdk含め諸々のアップデートがなされたわけですが、この直後から goapp コマンドでエラーが出るようになりました。
➜ goapp Traceback (most recent call last): File "/Users/shimbaroid/google-cloud-sdk/platform/google_appengine/goapp", line 13, in <module> from google.appengine.tools import goroots ImportError: cannot import name goroots
goroots
なるパッケージのimportに失敗しているようです。
このスクリプト(goapp)、pythonで書かれているので「pythonのパッケージいれればいいのかな」なんて思いましたが面倒くさそうなので避けました。pythonやったことないワカンナイ。
今回インストールされたappengineのバージョンは1.9.53でした。アップデート前のバージョンはわからないです…
➜ goapp version go version 1.6.3 (appengine-1.9.53) darwin/amd64
対処
件のgoroots
は$GOROOTを取得するためのもののようですが、別のマシンに入っている古いバージョンのgoapp
を確認したところ、goroots
を使わずに$GOROOTを取得していました。
これに倣って手元のマシンのgoappを書き換えてしまいます。
- from google.appengine.tools import goroots SDK_BASE = os.path.abspath(os.path.dirname(os.path.realpath(__file__))) - GOROOT = os.path.join(SDK_BASE, goroots.GOROOTS['go1']) + GOROOT = os.path.join(SDK_BASE, 'goroot')
古いマシンのappengineのバージョンは1.9.50でした。
以上です
【Ubuntu】自宅のマシンにssh接続して開発するためのセットアップメモ
自宅マシンに外出先からssh接続して開発を行おうと思い、サーバ・クライアントをセットアップしました。
この記事はLAN内でクライアントからサーバにssh接続するまでのメモです。WANから自宅LANにつなぐための、ルータやDNSの設定等の話はしていません。
参考にした記事は最後にまとめて記載しております。
想定環境
-
OS: Ubuntu 17.04
ユーザ名: host
ローカルipアドレス: 192.168.1.2
クライアント macbook pro 2012, vaio pro 13 mk2
OS: macOS Sierra 10.12.3, Ubuntu 17.04(Windows10上のVM)
2台のクライアントをセットアップしましたが、今回はmacOSでの解説のみ
セットアップ
クライアントとサーバを行ったり来たりしながら解説します
1. apt-get update (サーバ
sudo apt-get update
2. sshのインストール (サーバ
sudo apt-get install openssh-server
3. sshキーの生成 (クライアント
ssh-keygen
エンターキー3連打
4. 公開鍵をサーバに送る (クライアント
scpを利用してファイルを送信
scp ~/.ssh/id_rsa.pub user@192.168.11.2:~
user@192.168.11.2
の部分は手元の環境に合わせて置き換える。
5. 送られてきた公開鍵をauthorized_keysに登録する (サーバ
もし~/.ssh
が存在していなかったら
mkdir ~/.ssh
authorized_keysファイルをつくる
touch ~/.ssh/authorized_keys
authorized_keysに公開鍵を追記
cat ~/id_rsa.pub >> ~/.ssh/authorized_keys
6. sshd_configの設定 (サーバ
必要に応じてアクセス権の変更
sudo chmod a+w /etc/ssh/sshd_config
sshd_configを編集
vi /etc/ssh/sshd_config
Port XXXXX # ポートを22以外に変更 PermitRootLogin no PubkeyAuthentication yes AuthorizedKeysFile .ssh/authorized_keys PasswordAuthentication no ChallengeResponseAuthentication no
sshdの再起動
sudo /etc/init.d/ssh restart
7. クライアントからサーバへ接続できるか確認 (クライアント
sshd_configに設定したPortが44444であれば
ssh -p 44444 user@192.168.11.2
接続できなかった場合、サーバ側でauthorized_keysにちゃんと公開鍵が追記されているかとsshd_configに間違いがないかを確認。sshd_configを修正する場合はsshdの再起動を忘れない。
以上
「自宅に開発マシンおいて外から繋いで開発すれば、マシンも作業場所も選ばないじゃん」とおもってセットアップしたんですが、wi-fiの具合に超依存します。テザリングなんかじゃ快適とは程遠い。
その他ファイアーウォール等セキュリティの設定がありますが、今回はここまで。
参考
【Haskell】Stringの連結でハマった
ここ数日、「すごいHaskell楽しく学ぼう!」でHaskellしてます。関数型プログラミングを勉強してみたかった。
第4章を少し進めて再帰の考え方がわかってきたため、復習としてFizzBuzzを。
fizzbuzz :: Int -> [String] fizzbuzz n | n < 1 = [] fizzbuzz n = fizzbuzz (n-1) ++ [(fb n)] where fb n | n `mod` 15 == 0 = "FizzBuzz" | n `mod` 3 == 0 = "Fizz" | n `mod` 5 == 0 = "Buzz" | otherwise = show n
Intを引数にとり、1から引数で指定した数までをFizzBuzzします。
引数nを関数fbでFizzBuzz文字へ変換し、その先頭へ再帰でまたFizzBuzz文字をつなげています。
本題ですが、3行目のリストの連結で2時間ほどハマっていました。
リストの連結には2つ方法がある
リストの連結には2つの方法があります。 : を使う方法と ++ を使う方法です。
Prelude> let x = [3,5,7] Prelude> x ++ [5, 3] [3,5,7,5,3] Prelude> 5 : x [5,3,5,7] Prelude> 7 : 5 : x [7,5,3,5,7] Prelude>
これだけでは違いがわかりにくいですね。
これではどうでしょう。
Prelude> [5,3] ++ x [5,3,3,5,7] Prelude> x : 5 <interactive>:31:1: error: • Non type-variable argument in the constraint: Num [[t]] (Use FlexibleContexts to permit this) • When checking the inferred type it :: forall t. (Num [[t]], Num t) => [[t]] Prelude> x : 5 : 3 <interactive>:32:1: error: • Non type-variable argument in the constraint: Num [[t]] (Use FlexibleContexts to permit this) • When checking the inferred type it :: forall t. (Num [[t]], Num [t], Num t) => [[t]] Prelude>
++演算子は2つの値を入れ替えても動作します。
一方 : 演算子はエラーが発生しました。
演算子の定義を確認してみましょう。
Prelude> :t (++) (++) :: [a] -> [a] -> [a] Prelude> :t (:) (:) :: a -> [a] -> [a]
つまり、
というように考えられます。似ているようで似てなかった。
FizzBuzz書いているときは当該の行を
fizzbuzz n = (fizzbuzz (n-1)) : (fb n)
としておりました。エラーメッセージでも型の齟齬が吐かれていましたが、少しややこしいものでした。
キョウリョクナカタスイロン
先程の一行を型に注目するとこうなります。
fizzbuzz n = [String] : String
またエラーメッセージではこの行について
「(fizzbuzz (n-1))の返り値の型、[[String] ]になってんぞ。[String]じゃね?」
「(fb n)の返り値の型、[Char]になってんぞ。[[String] ]じゃね?」
と、強力な型推論のおかげ(せい)で余計に惑わされました。
以上です。
まとめ
haskellを触り始めて1日目、エラーにハマったおかげでかえって満足です。エラーメッセージとがっつり対峙できたため今後はスルスルと進めていきたいです。
【Retrofit2】jsonschema2pojoがUnexpected character was〜する
pojo変換用のclassをjsonschema2pojoにレスポンスをコピペして作るわけですが、ぼくの環境だとどうも
There's a problem: Unexpected character ('m' (code 109)): was expecting double-quote to start field name (line 2, column 2)
とエラーが出てしまいます。フィールドにあたる文字のクォーテーションが無いぞとのことです。
これは、GoogleChromeのJSONViewという拡張機能が原因でした。
この拡張機能はjsonのレスポンスを整形してchrome上で見やすくしてくれるものですが、フィールドにあたる文字のクォーテーションを外してしまいます。そのために上記のエラーが発生していました。
じゃあコピペのあとにキーひとつずつにクォーテーションをつければいいわけですが、ぼくはこのときだけSafariを使っています。生のjsonならpojo classを生成できます。
ありがとうございました。
【Android Studio】Api keyを始めとしたgit管理したくない定数を環境変数で管理する
Api keyやGoogle AnalyticsのトラッキングIDなんかは、ハードコーディングしてgithub等でオープンにするのが好ましくないですよね。
そこでOS Xの環境変数にそれらを登録しておき、Android Studioのビルドの際に参照するようにして回避しましょう。
OS Xの環境変数にkeyを登録する
ターミナルから以下を実行します。
$ launchctl setenv APIKEY hogehoge
APIKEYがkey、hogehogeがvalueにあたるKeyValue形式です。
build.gradleから環境変数を参照する
buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' // 環境変数を参照する manifestPlaceholders = [api_key: System.getenv("APIKEY")] } }
環境変数のAPIKEYを参照し、api_keyへ格納しています。
AndroidManifest.xmlからmanifestPlaceholdersを参照する
<application> <meta-data android:name="apiKey" android:value="${api_key}"/> </application>
javaプログラムの中からAndroidManifest.xmlのmeta-dataを参照する
String key = ""; try{ ApplicationInfo info = getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA); key = info.metaData.getString("apiKey"); }catch(PackageManager.NameNotFoundException e){ e.printStackTrace(); } // keyを使った処理〜〜
これでString keyはhogehogeになっています。
Windowsから環境変数を登録し参照することは検証していないです。
ありがとうございました。
参考
AndroidManifest.xmlにAPIキーを書いたコードをGitHubにコミットしないために AndroidManifest.xmlに記述したメタデータを取得する
【Jenkins】Ubuntu16.04にjenkinsをインストールする
多くがQiitaや他の技術ブログのコピペになってしまいますが、Ubuntu16.04にセットアップした話は見かけなかったのでここに残します。
JDKのインストール
$sudo add-apt-repository ppa:webupd8team/java $sudo apt-get update $sudo apt-get install oracle-java7-installer
Jenkinsのインストール
$wget -q -O - http://pkg.jenkins-ci.org/debian/jenkins-ci.org.key | sudo apt-key add - $sudo sh -c 'echo deb http://pkg.jenkins-ci.org/debian binary/ > /etc/apt/sources.list.d/jenkins.list' $sudo apt-get update $sudo apt-get install jenkins
nginxのインストール
$sudo apt-get install nginx
nginxのリバースプロキシの設定
$gedit /etc/nginx/sites/available/default
で設定ファイルを開き、以下のように編集。(保存ができない場合、下記参照)
server { listen 80 default_server; listen [::]:80 default_server ipv6only=on; root /usr/share/nginx/html; index index.html index.htm; # Make site accessible from http://localhost/ server_name localhost; location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; if (!-f $request_filename) { proxy_pass http://localhost:8080; break; } } }
※保存ができない場合、以下のコマンドで権限を変更してから再度編集。
$sudo chmod a+w /etc/nginx/sites-available/default
nginxを再起動
$sudo service nginx restart
これでブラウザから http://localhost/にアクセスするとjenkinsが起動していることが確認できます。
初回はパスワードを求められます。赤字でハイライトされているpathのinitialAdminPasswordのなかに記述されています。これを確認します。
initialAdminPasswordを確認する
$sudo chmod a+r+x /var/lib/jenkins/secrets $sudo chmod a+r+x /var/lib/jenkins/secrets/initialAdminPassword $cat /var/lib/jenkins/secrets/initialAdminPassword
これで表示された英数字列を先ほどのブラウザ画面にコピペしてContinueを押します。
pluginのインストールですが、Install suggested pluginsで問題ないかと思います。
インストール完了!
Jenkinsのインストールが完了しました。今回は以上です。
参考