Using ViewModel in Jetpack Compose

This is a short memo about using ViewModel in Jetpack Compose.

Add Dependencies

// To use 'viewModel()` in composable
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha07"

// To use `observeAsState' in composable
implementation("androidx.compose.runtime:runtime-livedata:1.0.0-rc02")

create ViewModel

  • I think ViewModel is the same to what it used to be in not Composable.
class MyViewModel(): VIewModel() {
    private val _name = MutableLiveData("")
    val name: LiveData<String> = _name
}

Observe LiveData in Compose

@Composable
fun NameComposable(viewModel: MyViewModel = viewModel()) {
    val name: String by viewModel.name

   // use name or change name
}

It works!

Androidでタイマーアプリを作る Jetpack Compose

こんにちは。

タイマーアプリとかカウントダウンアプリとか、まぁ呼び方は何でもよいですがフロントの勉強するってなるとおすすめですよね。

ってことで今回はAndroidの新しいUIシステムであるJetpack Composeでタイマーアプリを作るために、CircularProgressIndicatorという便利なコンポーズを見ていきましょう。続きがあるかは謎です。

はい、これだけです。これで50%プログレスが100×100で色は黒、太さは10dpのドーナッツ型のプログレスの出来上がりです。

CircularProgressIndicator(
    progress = 0.5f,
    modifier = Modifier
        .height(100.dp)
        .width(100.dp),
    color = Color.Black,
    strokeWidth = 10.dp
)

※中央寄せを行っています。

引数のprogressを下記のようにanimateFloatAsStateで状態として持ってあげればよいですね。

var progress by remember { mutableStateOf(0f) }

val animatedProgress by animateFloatAsState(
    targetValue = progress,
    animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec
)

逆回りにしたいときはanimateProgressを1->0に減らしていけばよいです。

Jetpack ComposeのonSizeChangedとonGloballyPositioned

こんにちは。 今日は Jetpack Composeの公式サンプルアプリの一つであるJetsurveyソースコードでお勉強しています。

そこで onSizeChangedonGloballyPositionedというものが出てきてドキュメントを読み調べてみました。

onSizeChanged

まずこいつですが、名前的にサイズが変更されたときに呼ばれるのかなと思いました。ただ挙動を見ている限りどうも最初も呼ばれている模様。下記のドキュメントに普通に書いていましたが最初のコンポーズ時とサイズが変わった時に呼ばれるみたいです。

[onSizeChanged] wiill only be invoked during the first time measurement or when the size has changed.

またonSizeChangedを下記のように子要素にも記述した場合、初回コンポーズ時は子要素から呼ばれます。(2 -> 1となる。) (これは子要素の大きさが決まって初めて親要素の大きさも決まっていく感じですかね?)

Box(
    modifier = Modifier.onSizeChanged { 1 }
) {
    Box(
        modifier = Modifier.onSizeChanged { 2 }
    )
}

onGloballyPositioned

次はこいつです。 要素の配置が変更されたときに呼ばれます。またドキュメントにもありますが、初回コンポーズ時にもしっかり呼ばれます。

Note that it will be called after a composition when the coordinates are finalized.

下記のような場合は親要素から呼ばれます。(1 -> 2)

Box(
    modifier = Modifier.onGloballyPositioned { 1 }
) {
    Box(
        modifier = Modifier.onGloballyPositioned { 2 }
    )
}

Jetpack ComposeのRememberとStateについて

こんにちは!今日はRememberMutableStateについてのメモです。

  • mutableStateofについて
    mutableStateOfはComposable内で監視可能な状態変数を作成します。`mutableStateOf()'で作成した変数の値が変わるとその値を読み込むComposableやラムダの再コンポーズが実行されます。

  • rememberについて
    rememberを使用することで再コンポーズ後もmutableStateで作成した変数を記憶しておくことができます。rememberがないとせっかく再コンポーズを行っても値を保持していないので値が初期化されています。

val name = mutableStateOf("")

// name.value = "hoge"などで値を変更するとnameを使用するComposableやラムダの再コンポーズが行われます。しかし実際に再Composeを行うとrememberがないので再度name=""で初期化されてしまいます。
var name = remember { "" }

// 反対にrememberだけだといくらnameの値を書き換えても再コンポーズは行われません。なぜならJetpack ComposesはStateオブジェクトが変更されたときのみ再コンポーズが行われるからです。

よって公式にもあるように下記のように書くことになります。

val name = remember { mutableStateOf("") }

参考

状態とJetpack Compose

Jetpack ComposeのCompositionLocalProviderとは

こんばんは。

Jetpack Composeの公式サンプルアプリの一つであるJetsurveyソースコードを読んでいてCompositionLoacalProviderというものが分からなかったため調べてみました。

どうやらCompositionLocalProviderのラムダ内では引数として渡さなくても事前定義した変数を使いまわせるみたいです。

例を見てましょう。

まず以下のようなデータクラスがあるとします。

data class CompositionData(
    val name: String = ""
)

次に `compositionLocalOf()' で変数のデフォルトを定義します。ラムダ内の処理がデフォルト値となるようです。

val LocalUser = compositionLocalOf {
    CompositionData()
}

最後に`CompositionLocalProvider()'を記述します。

CompositionLocalProvider(LocalUser provides CompositionData("gouta", 100)) {
    MyContent() //変数を使用したいCompose関数
}

'MyContent()'には引数として明示的にデータを渡していませんが以下のように使用することができます。

@Composable
fun MyContent() {
    val user = LocalUser.current
    Text(
        text = user.name,
        modifier = Modifier.fillMaxWidth(),
    )
}

まとめ

以上、CompositionLocalProviderの簡単な説明でした。変数がGlobalチックになるので使いまわしは危険そうです。しかし公式の実装にもあるようにContextの取得など他にも便利なものがあるので使ってみたいと思います。

  • LocalContext
  • 'LocalConfiguration'
  • 'LocalLifecycleOwner'

などなど

参考

Jetpack Compose Playground

Kenji Abeさんの記事

This version (1.0.0-beta06) of the Compose Compiler requires Kotlin version 1.4.32 but you appear to be using Kotlin version 1.4.30 which is not known to be compatible.

こんにちは。

Jetpack Composeを試そうとした際にビルドエラーが出たときの対処です。

This version (1.0.0-beta06) of the Compose Compiler requires Kotlin version 1.4.32 but you appear to be using Kotlin version 1.5.0 which is not known to be compatible.

ComposeのコンパイラはKotlinの1.4.32を使用しなければいけないのに、1.5.0を使っているから駄目だというかんじですね。

あれ、KotlinのVersionってどこで指定するんだっけ?

結論

プロジェクトのbuild.gradleに下記を追加すればよいです。

buildscript {
    dependencies {
        classpath "com.android.tools.build:gradle:7.0.0-alpha15"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.32" // これを追加
    }
}

感想

Kotlinは結局Gradleのプラグインとしてダウンロードされているのですね。。。これで簡単にKotlinのバージョンを指定できるようになりました。

Navigation ComponentでpopEnterAnimが効かない

今日はAndroidのNavigation Componentについてです。

早速ですがフラグメント間の遷移でアニメーションを付けるためにnavファイルのenterAnimexitAnimpopEnterAnimpopExitAnimをいじっていました。

実行してみたところなぜかpopEnterAnimのみアニメーションが反映されませんでした。

環境は以下です。

def nav_version = "2.3.5"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"

とりあえず修正方法

こちらより、以下を試してみたところ治りました。

  • gradleに以下の依存関係を使いすることで修正しました。
implementation 'androidx.fragment:fragment-ktx:1.3.3'

理由が分かったら追記する予定です。(なんでfragment-ktxを追加しただけで治ったのだろう?)