- 投稿日:2021-10-22T14:01:28+09:00
KMM kotlinx-serializationでクラッシュする時にやったこと
普段、iOSをやっていて、Androidは全く触っていないですが、KMMがずっと気になっていて某記事を参考に試しにやってみました。 慣れない中、クラッシュの連続で直すのが一番大変だったkotlinx-serializationでのエラーの直し方を残しておきます。 なかなかライブラリが参照されなかったり、クリーンしてようやく直ったと思ったら@ Serializablを使っているクラスが not foundでクラッシュしてしまいました。。 まず、shared内のbuild.gradleに下記を記述します。 plugins { kotlin("multiplatform") kotlin("plugin.serialization") version "1.4.10" //これを追加 id("com.android.library") } 次にandroidApp内のbuild.gradleに下記を記述します。 android { compileOptions { targetCompatibility = org.gradle.api.JavaVersion.VERSION_1_8 sourceCompatibility = org.gradle.api.JavaVersion.VERSION_1_8 } 私の場合、以上で直りました! 慣れていない為かgradleなかなか厄介でした
- 投稿日:2021-10-22T10:26:19+09:00
[jetpack compose]Row/ColumnとConstraintLayoutでどの程度レイアウトの作り方が違うのか
jetpack composeでかんたんなレイアウトをそれぞれ - Row/Column/Box (※今回Boxは使っていません) - Constraint で作成した場合、どのような違いが出るのか見てみました。 ConstraintLayoutのUIの作り方とかはこの記事では触れませんがコード見ていただいてもある程度理解できるかなと思います。 作ってみたのはこちら。 [search, share, home, star] というラベルが付いたアイコンを並べる という部分をそれぞれRow/Columを使って実現したパターンとConstraintLayoutを使って実現したパターンとなります。 @Composable fun RowColumnOrConstraintScreen() { Column( modifier = Modifier.padding(8.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { Text(text = "row,column composable") Card(modifier = Modifier.padding(16.dp)) { IconList( modifier = Modifier .padding(8.dp) .fillMaxWidth() ) } Text(text = "constraint composable") Card(modifier = Modifier.padding(16.dp)) { IconListConstraint( modifier = Modifier .padding(8.dp) .fillMaxWidth() ) } } } @Composable @Preview fun RowColumnOrConstraintScreenPreview() { RowColumnOrConstraintScreen() } こちらがプレビューの内容です Row/Column 以下を作ります。 基本となるComposable関数 IconLabel search, share, home, starそれぞれを表すComposable関数 IconLabelSearch, IconLabelShare, IconLabelHome, IconLabelStar 一列に並べるComposable関数 IconList @Composable fun IconLabel(imageVector: ImageVector, label: String, modifier: Modifier) { Column( modifier = modifier.padding(vertical = 8.dp), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, ) { Image( imageVector = imageVector, contentDescription = label, modifier = Modifier.padding(horizontal = 16.dp), ) Text(text = label) } } @Composable fun IconLabelSearch(modifier: Modifier = Modifier) { IconLabel(imageVector = Icons.Rounded.Search, label = "search", modifier = modifier) } @Composable fun IconLabelShare(modifier: Modifier = Modifier) { IconLabel(imageVector = Icons.Rounded.Share, label = "share", modifier = modifier) } @Composable fun IconLabelHome(modifier: Modifier = Modifier) { IconLabel(imageVector = Icons.Rounded.Home, label = "home", modifier = modifier) } @Composable fun IconLabelStar(modifier: Modifier = Modifier) { IconLabel(imageVector = Icons.Rounded.Star, label = "star", modifier = modifier) } @Composable fun IconList(modifier: Modifier = Modifier) { Row( modifier = modifier, horizontalArrangement = Arrangement.SpaceEvenly, ) { IconLabelSearch() IconLabelShare() IconLabelHome() IconLabelStar() } } @Composable @Preview fun IconListPreview() { IconList(modifier = Modifier.fillMaxWidth()) } こちらがプレビューの内容です。 ConstraintLayout Row/Columnと同様の構成で以下を作ります。 基本となるComposable関数 IconLabelConstraint search, share, home, starそれぞれを表すComposable関数 IconLabelSearchConstraint, IconLabelShareConstraint, IconLabelHomeConstraint, IconLabelStarConstraint 一列に並べるComposable関数 IconListConstraint @Composable fun IconLabelConstraint(imageVector: ImageVector, label: String, modifier: Modifier = Modifier) { ConstraintLayout( modifier = modifier ) { val (rateVal) = createRefs() Image(imageVector = imageVector, contentDescription = label, modifier = Modifier.constrainAs(rateVal) { top.linkTo(parent.top, margin = 8.dp) start.linkTo(parent.start, margin = 16.dp) end.linkTo(parent.end, margin = 16.dp) }) Text(text = label, modifier = Modifier.constrainAs(createRef()) { top.linkTo(rateVal.bottom) start.linkTo(rateVal.start) end.linkTo(rateVal.end) bottom.linkTo(parent.bottom, margin = 8.dp) }) } } @Composable fun IconLabelSearchConstraint(modifier: Modifier = Modifier) { IconLabelConstraint(imageVector = Icons.Rounded.Search, label = "search", modifier = modifier) } @Composable fun IconLabelShareConstraint(modifier: Modifier = Modifier) { IconLabelConstraint(imageVector = Icons.Rounded.Share, label = "share", modifier = modifier) } @Composable fun IconLabelHomeConstraint(modifier: Modifier = Modifier) { IconLabelConstraint(imageVector = Icons.Rounded.Home, label = "home", modifier = modifier) } @Composable fun IconLabelStarConstraint(modifier: Modifier = Modifier) { IconLabelConstraint(imageVector = Icons.Rounded.Star, label = "star", modifier = modifier) } @Composable fun IconListConstraint(modifier: Modifier = Modifier) { ConstraintLayout( modifier = modifier ) { val (search, share, home, star) = createRefs() IconLabelSearchConstraint( modifier = Modifier.constrainAs(search) { top.linkTo(parent.top) start.linkTo(parent.start) end.linkTo(share.start) }) IconLabelShareConstraint( modifier = Modifier.constrainAs(share) { top.linkTo(parent.top) start.linkTo(search.end) end.linkTo(home.start) }) IconLabelHomeConstraint( modifier = Modifier.constrainAs(home) { top.linkTo(parent.top) start.linkTo(share.end) end.linkTo(star.start) }) IconLabelStarConstraint( modifier = Modifier.constrainAs(star) { top.linkTo(parent.top) start.linkTo(home.end) end.linkTo(parent.end) }) } } @Composable @Preview fun IconListConstraintPreview() { IconListConstraint(modifier = Modifier.fillMaxWidth()) } こちらがプレビューの内容です。 xmlのプレビューと同様に成約のマージンなどが可視化されているのが良いですね。 どちらも同じレイアウトを実現できたが・・・ コードを見ていかがでしたでしょうか? このレベルのレイアウトならRow/Columnのほうがならべるだけで簡単で良さそうですね。 普段xmlでViewを作っているとついついViewを平たく保つためにConstraintLayoutを使ってしまいがちですが、 実際jetpack composeでConstraintLayoutを使ってみると結構な量で冗長なmodifierを書く必要があり(constrainAsの箇所のことです)、ちょっと読みづらいなと思いました。 Layout Inspectorでも見てみました ConstraintLayoutとして木構造になっているので階層構造がこのレベルだとあまり深さの違いはわかりませんでした・・・ パフォーマンスについて 公式のドキュメントに言及があります。 ページをそのまま引用させていただきます。 https://developer.android.com/jetpack/compose/layouts/constraintlayout?hl=ja 注: View システムでは、大規模で複雑なレイアウトを作成する場合のおすすめの方法は ConstraintLayout でした。ネストされたビューよりもフラットなビュー階層のほうがパフォーマンスが優れていたためです。しかし、深いレイアウト階層を効率的に扱える Compose では、このような懸念はありません。 注: Compose の特定の UI に ConstraintLayout を使用するかどうかは、デベロッパーの好みによります。Android View システムでは、より高パフォーマンスのレイアウトを作成する方法として ConstraintLayout が使用されていましたが、この点は Compose では問題になりません。選択する必要がある場合は、ConstraintLayout がコンポーザブルの読みやすさと保守性に役立つかどうかを検討します。 ConstraintLayoutはマルチプラットフォームではない ConstraintLayoutは現在マルチプラットフォーム化はされておらずAndroidで使えるライブラリとなっています。 Row/Column/Boxといった標準UIで構成できるようにしておくことで今後、 jetpack compose for Desktopやfor Webなどにも適用できるような基礎力が身につくのかなと思いました。 まとめ 実際ConstraintLayoutが煩雑になりやすそうなイメージを持ったので - 基本はRow/Column/BoxでUIを作っていく - 局所的に成約を設定したい場合にConstraintLayout使っても良さそう くらいの認識になりました。
- 投稿日:2021-10-22T06:07:57+09:00
DataStoreを試してみる
DataStoreとは? プロトコル バッファを使用して Key-Value ペアや型付きオブジェクトを格納できるSharedPreferencesの進化版 DataStore には、以下2 種類がある - Preferences DataStore - キーを使用してデータの保存およびアクセスを行います。この実装では、 定義済みのスキーマは必要ありませんが、タイプセーフではありません。 - Proto DataStore - カスタムデータ型のインスタンスとしてデータを保存します。この実装では、 プロトコル バッファを使用してスキーマを定義する必要がありますが、タイプセーフです。 試した環境 サンプル用にこちらにリポジトリ作成しています。 macOS Catalina ver 10.15.7 (intel版) Android Studio Arctic Fox | 2020.3.1 Pat ch 2 Preferences DataStore まずは Preferences DataStore を試してみます。早速プロジェクトを作成し、以下を app/build.gradle に追加します。 implementation "androidx.datastore:datastore-preferences:1.0.0" DataStore クラスと Preferences クラスを使用して、単純な Key-Value ペアをディスクに保持してみます。 Kotlinファイルのトップで以下を追加し、DataStoreをシングルトンとして扱えるようにします。 import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.preferencesDataStore val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings") 値を保存して読み込む処理を書いてみる シンプルに保存 -> 読み込みを行ってログ出力を行います。 val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings") val TEXT_KEY = stringPreferencesKey("example_text") class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... GlobalScope.launch { saveText(this@MainActivity, "sample") val textFlow: Flow<String> = dataStore.data.map { p -> p[TEXT_KEY] ?: "" } textFlow.collect { Log.d("DataStore", "text = $it") } } } } suspend fun saveText(context: Context, text: String) { context.dataStore.edit { settings -> settings[TEXT_KEY] = text } } 結果 DataStore: text = sample が出力されればOK Proto DataStore 次は Proto DataStore を試してみます。 環境構築 まずは以下内容でモジュール内の build.gradle を修正します。 plugins { id "com.google.protobuf" } dependencies { implementation "androidx.datastore:datastore:1.0.0" implementation "com.google.protobuf:protobuf-javalite:3.14.0" } protobuf { protoc { artifact = "com.google.protobuf:protoc:3.14.0" } // Generates the java Protobuf-lite code for the Protobufs in this project. See // https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation // for more information. generateProtoTasks { all().each { task -> task.builtins { java { option 'lite' } } } } } プロジェクトルートの build.gradle の dependencies に以下を追加します。 dependencies { classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.17' } 次に app/src/main/proto に settings.proto を作成します。 syntax = "proto3"; option java_package = "com.slowhand.datastoresample.model"; option java_multiple_files = true; message Settings { int32 example_counter = 1; } Settings.kt を以下内容で作成します。 package com.slowhand.datastoresample.model import android.content.Context import androidx.datastore.core.CorruptionException import androidx.datastore.core.DataStore import androidx.datastore.core.Serializer import androidx.datastore.dataStore import androidx.datastore.preferences.protobuf.InvalidProtocolBufferException import java.io.InputStream import java.io.OutputStream object SettingsSerializer : Serializer<Settings> { override val defaultValue: Settings = Settings.getDefaultInstance() override suspend fun readFrom(input: InputStream): Settings { try { return Settings.parseFrom(input) } catch (exception: InvalidProtocolBufferException) { throw CorruptionException("Cannot read proto.", exception) } } override suspend fun writeTo( t: Settings, output: OutputStream ) = t.writeTo(output) } val Context.settingsDataStore: DataStore<Settings> by dataStore( fileName = "settings.proto", serializer = SettingsSerializer ) 保存して読み込んでみます。 GlobalScope.launch { incrementCounter(this@MainActivity) val exampleCounterFlow: Flow<Int> = settingsDataStore.data .map { settings -> settings.exampleCounter } exampleCounterFlow.collect { Log.d("DataStore", "counter = $it")} } suspend fun incrementCounter(context: Context) { context.settingsDataStore.updateData { currentSettings -> currentSettings.toBuilder() .setExampleCounter(currentSettings.exampleCounter + 1) .build() } } counter = のログが出力されればOKです。 参考URL Jetpack DataStore入門〜Preferences DataStore実装編〜 - Sansan Builders Blog CoroutinesのlaunchとasyncとChannelとFlowの使い分け - Qiita Jetpack Compose の例 - たくさんの自由帳 コンポーザブルのライフサイクル | Jetpack Compose | Android Developers Android Jetpack Proto DataStore. A Jetpack recommended solution for… | by Satya Pavan Kantamani | ProAndroidDev 公式ページを眺めているだけではわからないJetpack DataStoreのあれこれ - Qiita
- 投稿日:2021-10-22T06:07:57+09:00
【Android】 DataStoreを試してみる
DataStoreとは? プロトコル バッファを使用して Key-Value ペアや型付きオブジェクトを格納できるSharedPreferencesの進化版 DataStore には、以下2 種類がある - Preferences DataStore - キーを使用してデータの保存およびアクセスを行います。この実装では、 定義済みのスキーマは必要ありませんが、タイプセーフではありません。 - Proto DataStore - カスタムデータ型のインスタンスとしてデータを保存します。この実装では、 プロトコル バッファを使用してスキーマを定義する必要がありますが、タイプセーフです。 試した環境 サンプル用にこちらにリポジトリ作成しています。 macOS Catalina ver 10.15.7 (intel版) Android Studio Arctic Fox | 2020.3.1 Pat ch 2 Preferences DataStore まずは Preferences DataStore を試してみます。早速プロジェクトを作成し、以下を app/build.gradle に追加します。 implementation "androidx.datastore:datastore-preferences:1.0.0" DataStore クラスと Preferences クラスを使用して、単純な Key-Value ペアをディスクに保持してみます。 Kotlinファイルのトップで以下を追加し、DataStoreをシングルトンとして扱えるようにします。 import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.preferencesDataStore val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings") 値を保存して読み込む処理を書いてみる シンプルに保存 -> 読み込みを行ってログ出力を行います。 val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings") val TEXT_KEY = stringPreferencesKey("example_text") class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... GlobalScope.launch { saveText(this@MainActivity, "sample") val textFlow: Flow<String> = dataStore.data.map { p -> p[TEXT_KEY] ?: "" } textFlow.collect { Log.d("DataStore", "text = $it") } } } } suspend fun saveText(context: Context, text: String) { context.dataStore.edit { settings -> settings[TEXT_KEY] = text } } 結果 DataStore: text = sample が出力されればOK Proto DataStore 次は Proto DataStore を試してみます。 環境構築 まずは以下内容でモジュール内の build.gradle を修正します。 plugins { id "com.google.protobuf" } dependencies { implementation "androidx.datastore:datastore:1.0.0" implementation "com.google.protobuf:protobuf-javalite:3.14.0" } protobuf { protoc { artifact = "com.google.protobuf:protoc:3.14.0" } // Generates the java Protobuf-lite code for the Protobufs in this project. See // https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation // for more information. generateProtoTasks { all().each { task -> task.builtins { java { option 'lite' } } } } } プロジェクトルートの build.gradle の dependencies に以下を追加します。 dependencies { classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.17' } 次に app/src/main/proto に settings.proto を作成します。 syntax = "proto3"; option java_package = "com.slowhand.datastoresample.model"; option java_multiple_files = true; message Settings { int32 example_counter = 1; } Settings.kt を以下内容で作成します。 package com.slowhand.datastoresample.model import android.content.Context import androidx.datastore.core.CorruptionException import androidx.datastore.core.DataStore import androidx.datastore.core.Serializer import androidx.datastore.dataStore import androidx.datastore.preferences.protobuf.InvalidProtocolBufferException import java.io.InputStream import java.io.OutputStream object SettingsSerializer : Serializer<Settings> { override val defaultValue: Settings = Settings.getDefaultInstance() override suspend fun readFrom(input: InputStream): Settings { try { return Settings.parseFrom(input) } catch (exception: InvalidProtocolBufferException) { throw CorruptionException("Cannot read proto.", exception) } } override suspend fun writeTo( t: Settings, output: OutputStream ) = t.writeTo(output) } val Context.settingsDataStore: DataStore<Settings> by dataStore( fileName = "settings.proto", serializer = SettingsSerializer ) 保存して読み込んでみます。 GlobalScope.launch { incrementCounter(this@MainActivity) val exampleCounterFlow: Flow<Int> = settingsDataStore.data .map { settings -> settings.exampleCounter } exampleCounterFlow.collect { Log.d("DataStore", "counter = $it")} } suspend fun incrementCounter(context: Context) { context.settingsDataStore.updateData { currentSettings -> currentSettings.toBuilder() .setExampleCounter(currentSettings.exampleCounter + 1) .build() } } counter = のログが出力されればOKです。 参考URL Jetpack DataStore入門〜Preferences DataStore実装編〜 - Sansan Builders Blog CoroutinesのlaunchとasyncとChannelとFlowの使い分け - Qiita Jetpack Compose の例 - たくさんの自由帳 コンポーザブルのライフサイクル | Jetpack Compose | Android Developers Android Jetpack Proto DataStore. A Jetpack recommended solution for… | by Satya Pavan Kantamani | ProAndroidDev 公式ページを眺めているだけではわからないJetpack DataStoreのあれこれ - Qiita