「Kotlinx Json」の登場でサードパーティJSONライブラリは不要となる。

JSONのライブラリ何を使ってますか?

👉 square/moshi: A modern JSON library for Kotlin and Java. 
👉 FasterXML/jackson: Main Portal page for the Jackson project 
👉 google/gson: A Java serialization/deserialization library to convert Java Objects into JSON and back 

これらは、Javaベースで書かれています。Kotlin は100%相互運用可能ですが微妙に期待しない挙動をします。


data class User(
    val name: String,
    val email: String,
    val age: Int = 13,
    val role: Role = Role.Viewer
)

enum class Role { Viewer, Editor, Owner }


{
    "name" : "John Doe",
    "email" : "[email protected]"
}


class JsonUnitTest {

    private val jsonString = """
            {
                "name" : "John Doe",
                "email" : "[email protected]"
            }
        """.trimIndent()

    @Test
    fun gsonTest() {
        val user = Gson().fromJson(jsonString, User::class.java)

        assertEquals("John Doe",user.name)
        assertEquals(null, user.role)
        assertEquals(0, user.age)

//      User(name=John Doe, 
//           [email protected], 
//           age=0, 
//           role=null)

    }

}

デフォルト値が期待通りにパースできません。

kotlinx.serialization が登場!!

JetBrains産です。間違いないでしょう。



👉 Kotlin/kotlinx.serialization: Kotlin multiplatform / multi-format serialization 

- クロスプラットフォーム
- 非リフレクション
- アノテーション @Serializable
- Kotlin v1.3.30+


@Serializable
data class User(
    val name: String,
    val email: String,
    val age: Int = 13,
    val role: Role = Role.Viewer
)

enum class Role { Viewer, Editor, Owner }

class JsonUnitTest {

    private val jsonString = """
            {
                "name" : "John Doe",
                "email" : "[email protected]"
            }
        """.trimIndent()

    @Test
    fun jsonTest() {
        val user = Json.parse(User.serializer(), jsonString)

        assertEquals("John Doe", user.name)
        assertEquals(Role.Viewer, user.role)
        assertEquals(13, user.age)

//      User(name=John Doe, 
//           [email protected], 
//           age=13, 
//           role=Viewer)

    }
}

Gson では無視されていたデフォルト値がきちんと使用されます。

以下のセットアップでどうぞ。


buildscript {
    ext.kotlin_version = '1.3.60'
    repositories { jcenter() }

    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
    }
}


apply plugin: 'kotlin' 
apply plugin: 'kotlinx-serialization'


repositories {
    jcenter()
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.14.0" // JVM dependency
}

👉 Kotlinx Json vs Gson - Juraj Kušnier - Medium 



今どきの「ネット接続してるかどうか」のコード。

ググると公式リファレンスが見つかる。


val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val activeNetwork: NetworkInfo? = cm.activeNetworkInfo
val isConnected: Boolean = activeNetwork?.isConnectedOrConnecting == true

しかし、このコードはAPI29で非推奨。

Note: getActiveNetworkInfo() was deprecated in Android 10. Use NetworkCallbacks instead for apps that target Android 10 (API level 29) and higher.

👉 Monitor connectivity status and connection metering 

Android 10 で通信状態の変更を監視するには、 ConnectivityManager.registerNetworkCallback() を使いましょう。

👉 Android 10 時代の Connectivity Monitoring - takasfz blog 

- コールバックの登録/解除のタイミングはライフサイクルに依存する。
- コールバック onAvailable()/onLost() は、非メインスレッド上で受信。

よって、MVVM上で使うとなると、LiveData化するのが良さげ。


class ConnectivityLiveData @VisibleForTesting internal constructor(private val connectivityManager: ConnectivityManager)
    : LiveData<Boolean>() {

    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
    constructor(application: Application) : this(application.getSystemService(Context.CONNECTIVITY_SERVICE)
        as ConnectivityManager)

    private val networkCallback = object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network?) {
            postValue(true)
        }

        override fun onLost(network: Network?) {
            postValue(false)
        }
    }

    override fun onActive() {
        super.onActive()

        val activeNetwork: NetworkInfo? = connectivityManager.activeNetworkInfo
        postValue(activeNetwork?.isConnectedOrConnecting == true)

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            connectivityManager.registerDefaultNetworkCallback(networkCallback)
        } else {
            val builder = NetworkRequest.Builder()
            connectivityManager.registerNetworkCallback(builder.build(), networkCallback)
        }
    }

    override fun onInactive() {
        super.onInactive()
        connectivityManager.unregisterNetworkCallback(networkCallback)
    }
}

👉 ConnectivityLiveData - AndroidPub 

まとめ

ただ「ネットが使える/使えない」を判定させるだけの実装がここまでややこしいSDKてのは問題じゃねえか?!

みんな混乱してるようにみえるけども。

もっといい感じがあれば教えてね!


今どきの Retrofit と LiveData で Coroutine

ありがとうございます。


👉 Retrofit 

ご存知の通り Retrofit2 では、サスペンドな関数も利用できるようになっております。

👉 SpaceX REST API で試す Retrofit の coroutine 対応 


// NewModel.kt
@GET("/feed/here/")
suspend fun getData(@Query("token") token : String) : Status


// NewRepository.kt
class Repository {
  var client = RetrofitService.createService(JsonApi::class.java)
  suspend fun getData(token : String) = client.getData(token)
}

ので、以下のようなこれまでのコードは、


// OldViewModel.kt
val data = MutableLiveData<Status>()

private fun loadData(token: String){
  viewModelScope.launch {
    val retrievedData = withContext(Dispatchers.IO) {
      repository.getData(token)
    }
    data.value = retrievedData
  }
}

シンプルに以下のように書けます。


// NewViewModel.kt
val data : LiveData<Status> = liveData(Dispatchers.IO) {
      val retrievedData = repository.getData(token)
      emit(retrievedData)
    }

ありがとうございます。

👉 Exploring new Coroutines and Lifecycle Architectural Components integration on Android 
👉 Using Retrofit 2 with Kotlin coroutines - ProAndroidDev 


SQLDelight で View を使うべし

👉 Drive your UI with SQLDelight’s views | Leandro Favarin 
👉 GitHub - cashapp/sqldelight: SQLDelight - Generates typesafe Kotlin APIs from SQL 

SQLDelight は、すべてのクエリーに対して自動的にモデルオブジェクトを作成します。

以下シンプルな名前付きクエリー。


bandsOrderedByName:
SELECT id, name
FROM band
ORDER BY name DESC;

bandsOrderedByAge:
SELECT id, name
FROM band
ORDER BY age;

これから以下が作成される。


data class BandsOrderedByName(id: String, name: String)

data class BandsOrderedByAge(id: String, name: String)

実際は、もっと複雑になります。

以下、join句を使ったクエリーの場合。


SELECT
  band.id,
  band.name,
  album.*
FROM band
JOIN album ON band.id = album.band_id;

SQL View を使うとエレガントになります。


👉 SQLite Query Language: CREATE VIEW 


CREATE VIEW bandWithAlbum AS
SELECT
  band.id,
  band.name,
  album.*
FROM band
JOIN album ON band.id = album.band_id;

bandsOrderedByName:
SELECT *
FROM bandWithAlbum
ORDER BY name DESC;

bandsOrderedByAge:
SELECT *
FROM bandWithAlbum
ORDER BY age;

SQLDelight は、BandWithAlbum タイプを生成します。

続いて、ページネーションの例。


count:
SELECT count(*)
FROM bandWithAlbum;

paged:
SELECT *
FROM bandWithAlbum
LIMIT ?
OFFSET ?;

SQLDelight が生成するモデルは、data クラスなので、DiffUtil コールバックはすぐに書けます。


object BandItemCallback : ItemCallback<BandWithAlbum>() {
  override fun areItemsTheSame(oldItem: BandWithAlbum, newItem: BandWithAlbum): Boolean {
    return oldItem.id == newItem.id
  }

  override fun areContentsTheSame(oldItem: BandWithAlbum, newItem: BandWithAlbum): Boolean {
    return oldItem == newItem
  }
}

また、enum クラスを使ったソートオプション。


enum class Sort { NAME, AGE }

fun bandsSorted(by: Sort): Flow<List<BandWithAlbum>> = when (by) {
  NAME -> db.bandsOrderedByName()
  AGE -> db.bandsOrderedByAge()
}.asFlow().mapToList()

逆に、これらのようなSQL処理をプログラムで実行すると効率は落ちます。
👉 The Resurgence of SQL (Droidcon NYC 2017) - Speaker Deck 

まとめ

欲しいタイプを View にすると、少ないコードで実現できます。

ユーザーの要求は、技術が発達するにつれてますます激しくなることは明らかです。良きユーザエクスペリエンスのための簡単な実装方法を常に把握しておくことが重要になります。


AndroidJUnit4.class is deprecated | androidx.test.InstrumentationRegistry is deprecated

地味にハマる。

こーゆーのは、いちいちググらないといけないのか?

誰の都合の変更だ?

以下、苦悩の動画。



ここら androidx.test.ext に注意していないと分からない。

👉 Google's Maven Repository 


dependencies {

    // …

    androidTestImplementation 'androidx.test:core:1.2.1-alpha02'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2-alpha02' // deprecated AndroidUnitTest4

そういういうことですか。

👉 android - AndroidJUnit4.class is deprecated: How to use androidx.test.ext.junit.runners.AndroidJUnit4? - Stack Overflow 
👉 android - androidx.test.InstrumentationRegistry is deprecated - Stack Overflow 


Related Categories :  AndroidappsDevelopmemtKotlin