Android Kotlin LiveData 超簡単なサンプルコード

超簡単にLiveDataを説明すると、

「データが変わるとビューが変わる」

ってことが簡単に実装できる方法です。

Android 本家DeveloperサイトのLiveData公式説明は下記をどうぞ。
https://developer.android.com/topic/libraries/architecture/livedata?hl=ja#work_livedata

リスナーやオブザーバーを自分で作ったりしなくてもいいので簡単ですし、メモリーリークが発生しない、アクティビティのライフサイクルのことを気にしなくてよいので使い勝手がよいです。

ViewModelというのを使わないといけないので(多分、使わなくてもいいんだろうけど使うことを推奨)ViewModelについても知っていた方がいいですが、これも超簡単に説明すると、

「ビューのデータ部分」

ということです。

gradleなどへの記述は、上記公式サイトを見てください。

ではいっきまーす。

//LiveDataActivity.kt内の記述
import android.arch.lifecycle.Observer //Android.utilのやつではないので注意
import android.arch.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.View
import android.widget.Button
import android.widget.TextView
import jp.onlineconsultant.taskadmin.ui.main.TaskViewModel


class LiveDataActivity : AppCompatActivity() {

    private lateinit var model: TaskViewModel


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

        val hello: TextView = findViewById(R.id.hello)

        val button = findViewById<View>(R.id.button) as Button
        button.setOnClickListener {

            model.currentTask.setValue("ほげぇぇ")

        }


        // Get the ViewModel.
        model = ViewModelProviders.of(this).get(
            TaskViewModel::
            class.java
        )

        // Create the observer which updates the UI.
        val nameObserver = Observer<String> { newName ->
            // Update the UI, in this case, a TextView.
            hello.text = newName
        }

        // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
        model.currentTask.observe(this, nameObserver)

        model.currentTask.value = "ほげほげ"
    }

}
<!-- activity_live_data.xmlの記述-->
<?xml version="1.0" encoding="utf-8"?>
<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=".LiveDataActivity">

    <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

        <TextView
                android:id="@+id/hello"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Hello World!" />

        <Button
                android:id="@+id/button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerHorizontal="true"
                android:layout_centerVertical="true"
                android:text="@string/task_input_voice" />
    </LinearLayout>

</android.support.design.widget.CoordinatorLayout>
//TaskViewmodel.kt内に記載
class TaskViewModel : ViewModel() {

        // Create a LiveData with a String
        val currentTask: MutableLiveData<String> by lazy {
            MutableLiveData<String>()
        }

    }

ボタンをクリックすると、

「ほげほげ」


「ほげぇぇ」

に変わります。

Kotlin ジェネリクスの型投影 変位指定 共変でout Anyとは をサンプルコードと共にめちゃ簡単に説明してみる

Kotlin勉強中の私です。Javaから移行組です.

今回、次のようなソースコードを見ていて、疑問に思ったことがいくつかありました。出典は、Android StudioでLoginの機能を自動的に作ってみたところ、Loginの結果を表示するクラスとして、下記のResultクラスが存在しています。

sealed class Result<out T : Any> {

    data class Success<out T : Any>(val data: T) : Result<T>() 
    data class Error(val exception: Exception) : Result<Nothing>()

    override fun toString(): String {
        return when (this) {
            is Success<*> -> "Success[data=$data]" 
            is Error -> "Error[exception=$exception]"
        }
    }
}

特に気になったのは

data class Success<out T : Any>(val data: T) : Result<T>() 

の部分ですね。

ジェネリクスは、Javaでも使っていたので知ってはいましたが、 out ?? ってなりました。

そこで、初めて「変位指定」とか「型投影」とか「不変」「共変」「反変」という言葉たちに出会いました。

もう、いきなりつまずきそうですよね?!字からして、難しそうで、逃げたくなりますよね??

「共変とはなんぞや?変位指定とはなんぞや!?」

と独眼鉄になっちゃいますね(´ω`)

しかも、説明を読んでも、さっぱりわかりませんでした。
特に、outがスーパータイプを指定することはわかりましたが、Anyって指定すると、なんでもいいわけじゃん?なんで必要なの?となりました。

Kotlin公式サイト ジェネリクス
https://dogwood008.github.io/kotlin-web-site-ja/docs/reference/generics.html

自分でコード書いてみたら、腑に落ちました。(もっと早くやれよ)

class Container<T>(var value:T)

上記のように、Containerクラスを作ります。とってもシンプルなクラスですね。ただ単に、入れ物です。value というプロパティだけがあります。

下記のようなshowというメソッドがあって、引数にContainerを指定します。(Android Studioで実行しています。)

    
fun show(container:Container<out Any>){
        Log.d(TAG, container.toString())
}

show()を呼び出します。

val inta:Container<Int> = Container(23)
show(inta)

val stringa:Container<String> = Container("うきき")
show(stringa)

今、ここでIntもStringもshowの引数として入れられましたね?

では、showメソッドの引数を、次のように引数の型指定から「out」をなくしてみます。

 fun show(container:Container<Any>){ //この行からoutがなくなっています。
Log.d(TAG, container.toString())
}
val inta:Container<Int> = Container(23)
show(inta)

val stringa:Container<String> = Container("うきき")
show(stringa)

すると、呼び出し元のshow(inta)、show(stringa)でコンパイルエラーが起こります。

Type mismach.Required Containre<Any>

って出ます。

ああ。つまり、outを指定するのは、Anyのサブタイプならなんでもいいよってためにつけるわけですね。٩( ‘ω’ )و

「ウォーーーーーターーーーー!!!」

とヘレンケラーが叫んだ瞬間もこんな感じだったでしょう。(大げさ)

これが、共変です。

反変はその反対です。(ざっくり)

不変は、outを消したときと同様です。

それにしても共変って日本語わかりにくくないですか??!「共に」「変わる」ってなんだ?オラさっぱりわからなかったぞ!!

初心者なので、理解が間違っているところがあるかもです。(´;ω;`)
ぜひぜひご指摘くださーい。

versions.gradle にバージョンを列記する

完全にただのメモなんですが。

Androidのサンプルコード見てたら、アプリケーションのgradle(build.gradle Module:app)に次のように書いてあって、?!と思いました。

android {
compileSdkVersion build_versions.target_sdk
buildToolsVersion build_versions.build_tools
///中略
}

build_versions.target_sdk

ってどこで定義されてるのかな?って思ったら、versions.gradleというファイルがあって、そこにバージョンたちが列記されていました。


def build_versions = [:]
build_versions.min_sdk = 14
build_versions.target_sdk = 28
build_versions.build_tools = "28.0.3"
ext.build_versions = build_versions

すごい。バージョンなどをいじるときに、アプリケーションのgradleファイルをいじらなくていいんですね。

設定は、プロジェクトのgradleファイル(build.gradle(Project:BasicSample)に次のように書いたらよいようです。

buildscript {
apply from: 'versions.gradle'
}


Smart cast to “TextView” is impossible, because “textView” is a mutable property…

JavaからKotlinへ移行してAndroidアプリ書いている、私のような人にはタイトルのようなエラー結構出ると思うんですけど。

下記のようなシンプルなコードがあったとします。

    private var textView: TextView? = null

    //アプリが開始されると最初に処理されるメソッド
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.content_input_voice)

        textView = findViewById<View>(R.id.textView) as TextView

        textView.setText(getText(R.string.no_task_error)) //ここでエラー
}

これは、結論から言いますと、次の2パターンで治ります。

//パターン1 textViewはnull許可だってわかってるよ?とする
textView?.setText(getText(R.string.no_task_error)) 
//パターン2 textViewをvalにする
val textView:TextView = findViewById<View>(R.id.textView) as TextView
textView.setText(getText(R.string.no_task_error))

mutable は可変という意味です。

varという宣言していたり、Null許可している変数にアクセスしようとすると出ます。


Class ‘Task’ is not abstract and does not implement abstract member public abstract fun writeToParcel(p0: Parcel!, p1: Int): Unit defined in android.os.Parcelable

AndroidのParcelのことでつまづています。

Parcelという仕組みは、例えばIntentからIntentへ何か値を渡すときに、String(文字列)とかInt(整数)とかだと簡単に渡せるんですが、自分で作った自作クラスのオブジェクトを渡すための仕組みです。(かなりはしょっていますが)

Parcelについて(Android公式 英語)

で、Javaでやってた時はかなりごちゃごちゃ書かないと、 自分で作った独自クラス にParcelというのを実装できませんでした。

Kotlinでも、少し古いのを読むと、の独自クラスに実装するのに、やはりかなりわかりにくい処理を書かねばいけませんでした。

しかし、なんと!!

Android StudioのKotlinのプラグインの1.3.60 以降では、下記のように

@Parcelize
class Task(val _id: String, val _name: String):Parcelab

これだけでOKになりました!

@Parcelize

アノテーションと、Parcelableを継承するだけで完結!

になったらしいです。

A study of the Parcelize feature from Kotlin Android Extensions
https://medium.com/@BladeCoder/a-study-of-the-parcelize-feature-from-kotlin-android-extensions-59a5adcd5909

しゅごいー

と思って早速実装したところ、タイトル通り、ビルドできませんでした。

下記のStack Overflowさんにも、1.3.60以降はこのエラー出ないヨ!って書いてあるんですけどね…。

https://stackoverflow.com/questions/56018761/class-x-is-not-abstract-and-does-not-implement-fun-writetoparcel-defined-in-an

仕方なく

androidExtensions {
experimental = true
}

をアプリケーションのbuild.gradleに書いたらビルドできるようになりました。

build.gradleの全部をサンプルで載せておきます。

apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "hogehoge.myapplication"
        minSdkVersion 14
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    sourceSets {
        main.java.srcDirs += 'src/main/kotlin'

        androidTest.java.srcDirs += 'src/androidTest/kotlin'
    }
}

androidExtensions {
    experimental = true
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    implementation 'com.android.support:design:28.0.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}