Kotlinスタートブック を参考に、Android および Java で利用できる、HTTP クライアント、Retrofit を使用して、Web API (JSON)を Kotlinのモデルとして扱う。

驚くほど簡単。KotlinスタートブックはAndroidアプリをKotlinで作成するための要点が端的にまとめられている。個人的にはもう少し文章密度が高いほうが好みだが、手法をシンプルに知るにはこちらのほうが良いかも。

1.build.gradle (Module app)

build.gradle に以下を追加、retrofitから、リアクティブプログラミングのRxJava、Googleが提供するJSONとJavaオブジェクトを相互変換ライブラリGSONの設定を行う

dependencies {
            :
    def retrofitVersion = '2.4.0'
    compile "com.squareup.retrofit2:retrofit:$retrofitVersion"
    compile "com.squareup.retrofit2:adapter-rxjava:$retrofitVersion"
    compile "com.squareup.retrofit2:converter-gson:$retrofitVersion"
}

2.Model の定義

data class Category(
        val id: String,
        val name: String,
        val subCategoryList: List,
        val priority: Int
)

data class SubCategory(
        val id: String,
        val name: String,
        val priority: Int
)

3.APIのインターフェースを定義

interface ApiClient {
    @GET("/category")
    fun category(@Query("query") query: String) : Observable<List<Category>>
}

4.API呼び出し例

下記で記述するJSONを取得する、Webサービスを呼び出し、取得出来たらLogcatに内容を吐き出す。

private fun apiCategoryFetch() {
    val gson = GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create()
    val retrofit = Retrofit.Builder()
            .baseUrl(CATEGORY_API_URL)
            .addConverterFactory(GsonConverterFactory.create(gson))
            .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
            .build()
    val apiClient = retrofit.create(ApiClient::class.java)
    apiClient.category("")
            .subscribeOn(Schedulers.io())
            .subscribe({
                for (test in it) {
                    Log.i(TAG, String.format("%s", test))
                }
            }, {
                Log.w(TAG, "")
            })
}

5.JSON サンプル

以下のJSONを取得する。

[
  {
    "id": "690d2606-27a3-4fc5-96e9-a8c614c891da", 
    "name": "Category[0]", 
    "priority": 0, 
    "sub_category_list": [
      {
        "id": "1ac76014-d5f5-489e-bb7d-692a24544fd9", 
        "name": "SubCategory[0,0]", 
        "priority": 0
      }, 
      {
        "id": "69b5c1ab-59f9-4d73-8b26-1de678aa6fa9", 
        "name": "SubCategory[0,2]", 
        "priority": 2
      }, 
      {
        "id": "a6be8f99-653d-4eea-8cf5-0add0133bbb2", 
        "name": "SubCategory[0,1]", 
        "priority": 1
      }
    ]
  }, 
  {
    "id": "067a1604-d9da-4676-9c2f-7a0d1dbed6ec", 
    "name": "Category[1]", 
    "priority": 1, 
    "sub_category_list": [
      {
        "id": "4f5c8387-4b5e-47b5-9a3f-7890ba7ea840", 
        "name": "SubCategory[1,0]", 
        "priority": 0
      }, 
      {
        "id": "e789a851-8b06-4612-9ff3-fa4a70b15df9", 
        "name": "SubCategory[1,2]", 
        "priority": 2
      }, 
      {
        "id": "c1d66227-22b0-4fab-8500-953c78bb7a84", 
        "name": "SubCategory[1,1]", 
        "priority": 1
      }
    ]
  }, 
  {
    "id": "c4824082-d727-4dcb-9341-8f734d83dcf2", 
    "name": "Category[2]", 
    "priority": 2, 
    "sub_category_list": [
      {
        "id": "7d4e1da9-604e-47e9-9d64-4d0831d3335d", 
        "name": "SubCategory[2,0]", 
        "priority": 0
      }, 
      {
        "id": "9ff87596-f129-4bc0-bfac-427546e58809", 
        "name": "SubCategory[2,2]", 
        "priority": 2
      }, 
      {
        "id": "2ad260c8-9d11-4188-b596-49bc98e4987f", 
        "name": "SubCategory[2,1]", 
        "priority": 1
      }
    ]
  }
]

6.取得結果のLog

いい感じで取得できた。

07-01 23:16:09.976 22795-22849/info.typea.hoge I/@@@@@ MainActivity: Category(id=690d2606-27a3-4fc5-96e9-a8c614c891da, name=Category[0], subCategoryList=[SubCategory(id=1ac76014-d5f5-489e-bb7d-692a24544fd9, name=SubCategory[0,0], priority=0), SubCategory(id=69b5c1ab-59f9-4d73-8b26-1de678aa6fa9, name=SubCategory[0,2], priority=2), SubCategory(id=a6be8f99-653d-4eea-8cf5-0add0133bbb2, name=SubCategory[0,1], priority=1)], priority=0)
    Category(id=067a1604-d9da-4676-9c2f-7a0d1dbed6ec, name=Category[1], subCategoryList=[SubCategory(id=4f5c8387-4b5e-47b5-9a3f-7890ba7ea840, name=SubCategory[1,0], priority=0), SubCategory(id=e789a851-8b06-4612-9ff3-fa4a70b15df9, name=SubCategory[1,2], priority=2), SubCategory(id=c1d66227-22b0-4fab-8500-953c78bb7a84, name=SubCategory[1,1], priority=1)], priority=1)
07-01 23:16:09.986 22795-22849/info.typea.sentence2 I/@@@@@ MainActivity: Category(id=c4824082-d727-4dcb-9341-8f734d83dcf2, name=Category[2], subCategoryList=[SubCategory(id=7d4e1da9-604e-47e9-9d64-4d0831d3335d, name=SubCategory[2,0], priority=0), SubCategory(id=9ff87596-f129-4bc0-bfac-427546e58809, name=SubCategory[2,2], priority=2), SubCategory(id=2ad260c8-9d11-4188-b596-49bc98e4987f, name=SubCategory[2,1], priority=1)], priority=2)

久々にAndroid。Androidにかかわらず、何かサービスなりアプリケーションなり作成しようとすると、ログインの仕組みだとか作るのが面倒くさい、というかそのあたりを作り上げると力尽きてそもそも何がしたかったのかわからなくなることがままある。

自分を含め、という人のために、Baas(Backend as a service) という、バックエンド機構をサービスとして提供する考え方があり、GoogleのBaas、Firebaseを使用して、Androidアプリにメール認証機能を組み込む。

1.Firebaseアカウントを作成しメール認証を有効化

Firebaseにアカウントを登録して、プロジェクトを登録。無料で始められる

https://firebase.google.com/?hl=ja

firebase_auth_getstart

ログインプロバイダ、メール/パスワードを有効にする

firebase_auth_mail_enable

2.Android Studio でプロジェクトを作成しFirebaseアシスタントを使用

https://firebase.google.com/docs/android/setup?authuser=0

Tools から、Firebaseを選択すると、画面の右にチュートリアルが起動

firebase_assistant

Authentication の、Email and password authentication を展開

firebase_assistant2

ウィザード形式で手順が表示されるので、それに従う。

JavaのサンプルコードをなれないKotlinに読み替えつつ。

3.プログラムの作成

とりあえず、画面にメールアドレスとパスワード入力欄、ユーザー作成と、サインインボタンを配置し、MainActivityに上記チュートリアルのコードをKotlinに読み替えつつ、動作を簡易に確認できるレベルで実装してみる。

package info.typea.sentence2

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import com.google.android.gms.tasks.Task
import com.google.firebase.auth.AuthResult
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseUser

class MainActivity : AppCompatActivity() {
    companion object {
        const val TAG: String = "MainActivity"
    }
    private var mAuth: FirebaseAuth? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        mAuth = FirebaseAuth.getInstance()

        // ユーザー登録ボタン
        var btnCreate: Button = findViewById<Button>(R.id.btnCreate) as Button
        btnCreate.setOnClickListener{
            var email = (this.findViewById<EditText>(R.id.txtEmail) as EditText).text.toString()
            var password = (this.findViewById<EditText>(R.id.txtPassword) as EditText).text.toString()
            Log.i(TAG, String.format("create email=%s, password%s", email, password))
            createUserWithEmailAndPassword(email, password )
        }

        // サインインボタン
        var btnSignIn: Button = findViewById<Button>(R.id.btnSignIn) as Button
        btnSignIn.setOnClickListener{
            var email = (this.findViewById<EditText>(R.id.txtEmail) as EditText).text.toString()
            var password = (this.findViewById<EditText>(R.id.txtPassword) as EditText).text.toString()
            Log.i(TAG, String.format("signin email=%s, password%s", email, password))
            signInWithEmailAndPassword(email, password)
        }
    }

    override fun onStart() {
        super.onStart()

        var currentUser = mAuth?.currentUser
        updateUI(currentUser)
    }

    // 画面表示更新
    private fun updateUI(currentUser:FirebaseUser?) {
        Log.i(TAG, String.format("update ui user=%s", currentUser?.email.toString()))
        (findViewById<TextView>(R.id.lblUser) as TextView).text = currentUser?.email.toString()
    }

    // ユーザー登録処理
    private fun createUserWithEmailAndPassword(email: String, password: String) {
        mAuth?.createUserWithEmailAndPassword(email, password)?.addOnCompleteListener{
            task: Task<AuthResult> ->
                if (task.isSuccessful) {
                    Log.d(TAG, "createUserWithEmail:success")
                    var user = mAuth?.currentUser
                    updateUI(user)
                } else {
                    Log.w(TAG, "createUserWithEmail:failure")
                    Toast.makeText(this, "Authentication failed",Toast.LENGTH_SHORT).show()
                    updateUI(null)
                }
        }
    }

    // サインイン処理
    private fun signInWithEmailAndPassword(email: String, password: String) {
        mAuth?.signInWithEmailAndPassword(email, password)?.addOnCompleteListener{
            task: Task<AuthResult> ->
                if(task.isSuccessful) {
                    Log.d(TAG, "signInWithEmail:success")
                    var user = mAuth?.currentUser
                    updateUI(user)
                } else {
                    Log.w(TAG, "signInWithEmail:failure")
                    Toast.makeText(this, "Authentication failed",Toast.LENGTH_SHORT).show()
                    updateUI(null)
                }
        }
    }
}

4.実行

メールアドレスとパスワード(6桁以上)を設定し、CREATEボタンを押下

処理が成功して、画面最上段にメールアドレスが表示された。

firebase_auth_screen

Firebaseの管理画面上にも、入力したメールアドレスが登録された!

これは捗る。

firebase_auth_success

windows 10 Pro + hyper-v で、

vagrant up –provider=hyperv

としたところ、以下のエラー

PS C:\workspaces\vm\centos7> vagrant up --provider=hyperv
Bringing machine 'default' up with 'hyperv' provider...
==> default: Verifying Hyper-V is enabled...
==> default: Importing a Hyper-V instance
    default: Cloning virtual hard drive...
    default: Creating and registering the VM...
An error occurred while executing a PowerShell script. This error
is shown below. Please read the error message and see if this is
a configuration error with your system. If it is not, then please
report a bug.

Script: import_vm_xml.ps1
Error:

C:\Programs\Vagrant\embedded\gems\2.1.1\gems\vagrant-2.1.1\plugins\providers\hyperv\scripts\import_vm_xml.ps1 : パラメ
ーター名 'switchid' に一致するパラメーターが見つかりません。
発生場所 行:1 文字:327
+ ... achines/default/hyperv/Virtual Hard Disks/disk.vhd' -switchid 'c08cb7 ...
+                                                         ~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [import_vm_xml.ps1]、ParameterBindingException
    + FullyQualifiedErrorId : NamedParameterNotFound,import_vm_xml.ps1

Hyper-VでVagrant upできない人はこれを見てみて (Vagrant 2.1.1対応方法更新しました)

の対応方法に従い、レジストリを修正

コンピューター\HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\vmsmp\parameters\SwitchList

vagrant_regedit

するも、エラー修正されず。。。

https://github.com/hashicorp/vagrant/issues/9774

上記スレッドに解決しそうなコメントがあったので、試す。

以下のスクリプトを編集

{インストールディレクトリ}Vagrant\embedded\gems\2.1.1\gems\vagrant-2.1.1\plugins\providers\hyperv\scripts\import_vm_xml.ps1

ダミーパラメーターを追加

[string]$switchid

スイッチ名を先頭行に指定(値は、上記レジストリエディタで指定した値)

$switchname = "DefaultSwitch"

Param(
          :
    [string]$enable_virtualization_extensions=$False,
    [string]$switchid
)
$switchname = "DefaultSwitch"

再度 vagrant up –provider=hyperv

vagrant_success

成功した!

vagrant_hyperv

SSHでの接続もOK