【Android】Kotlin/KodeinではじめてのDI

Kotlin/Kodeinを用いてはじめてのDIをしてみましょう

初めてDIに触れる人を対象読者としています。Kodeinの詳細な実装については解説しておりません。

DIとはなにか、なにがありがたいのか

DIとはDipendency Injectionの略で、日本語だと依存性注入と言われます。あるクラスAから依存するクラスBがあるとして、その依存関係を外部から指定してあげるものです。

↓Non DIの場合

f:id:shimbaroid:20170611005502p:plain

↓DIの場合

f:id:shimbaroid:20170611005533p:plain

クラスAでは依存先の抽象だけを指定し具象を外部から注入することで、コンポーネント間の依存度が下がり、疎結合なプログラムを構築することができます。

具体例を挙げます。web apiを叩きデータをダウンロードしてくるアプリがある場合、デバッグ中はハードコードした値を使いたいとか、デバッグ用サーバにつなぎたいということがあると思います。

f:id:shimbaroid:20170611005549p:plain

そんなときには外部のほうで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の実装については公式のドキュメントが充実しているので、参考にしてみてください。

ありがとうございました。

参考

Kodein

猿でも分かる! Dependency Injection: 依存性の注入

Dagger 2 から Kotlin製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でした。

以上です

google cloud sdkのインストールのときもinstall.sh書き直してたな…

【Ubuntu】自宅のマシンにssh接続して開発するためのセットアップメモ

自宅マシンに外出先からssh接続して開発を行おうと思い、サーバ・クライアントをセットアップしました。

この記事はLAN内でクライアントからサーバにssh接続するまでのメモです。WANから自宅LANにつなぐための、ルータやDNSの設定等の話はしていません。

参考にした記事は最後にまとめて記載しております。

想定環境

セットアップ

クライアントとサーバを行ったり来たりしながら解説します

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の具合に超依存します。テザリングなんかじゃ快適とは程遠い。

その他ファイアーウォール等セキュリティの設定がありますが、今回はここまで。


参考

SCPコマンドでローカルのファイルをサーバにアップ&サーバ上のファイルをDL

そこそこセキュアなlinuxサーバーを作るLinux

サーバに新たなSSH公開鍵を追加する

【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]

つまり、

  • ++演算子は「2つのリストを連結する」
  • : 演算子は「後者のリストの先頭に前者を追加する」

というように考えられます。似ているようで似てなかった。

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)

f:id:shimbaroid:20160818234319p:plain

とエラーが出てしまいます。フィールドにあたる文字のクォーテーションが無いぞとのことです。

これは、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が起動していることが確認できます。 f:id:shimbaroid:20160624164949p:plain

初回はパスワードを求められます。赤字でハイライトされている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を押します。 f:id:shimbaroid:20160624171121p:plain

pluginのインストールですが、Install suggested pluginsで問題ないかと思います。

インストール完了!

f:id:shimbaroid:20160624171520p:plain

Jenkinsのインストールが完了しました。今回は以上です。

参考

UbuntuにJenkinsをインストールする