Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Jetpack Compose ことはじめ / the beginning of Jetpack Compose

kenken
August 24, 2019

Jetpack Compose ことはじめ / the beginning of Jetpack Compose

kenken

August 24, 2019
Tweet

More Decks by kenken

Other Decks in Programming

Transcript

  1. Jetpack Compose
    ことはじめ
    @tkhs0604
    #kotlinfest
    Kotlin Fest 2019 @ 東京コンファレンスセンター・品川

    View Slide

  2. ● 高橋 健太
    ○ kenken | @tkhs0604
    ○ https://tkhs0604.hatenablog.com
    ● Gunosy Inc.
    ● SI営業→エンジニア(iOS/Android/Web)→Androidエンジニア
    ● アカペラ
    ○ 最近ORICON NEWSに少しだけ載りました
    自己紹介

    View Slide

  3. 本セッションの目標
    ● Jetpack Composeのさわりを理解する
    本日のアジェンダ
    ● Jetpack Compose とは
    ● サンプルアプリ
    ● コンポーネントの作成方法
    ○ @Composable
    ○ @Model
    本セッションの目標 & 本日のアジェンダ

    View Slide

  4. Jetpack Compose とは

    View Slide

  5. ● Google I/O 2019
    ● Android Jetpack
    ● Kotlin
    ● 宣言的UI
    ○ cf. Flutter、React Native、Vue.js、SwiftUI
    ● Pre-alpha版 (2019/8/23時点)
    ○ ⚠大きく変更が入る可能性があります
    Jetpack Compose とは

    View Slide

  6. サンプルアプリ

    View Slide

  7. ToDoアプリ
    ● タスクをリスト形式で表示
    ● アイテムまたはチェックボックスを
    タップすると、対応するタスクの状態
    (完了 or 未完了)が切り替わる

    View Slide

  8. コンポーネントの作成方法

    View Slide

  9. @Composable & @Model
    @Composable
    ● 関数に付与することで、コンポーネントとして認識される
    @Model
    ● クラスに付与することで、コンポーネント内で状態を扱うためのクラ
    スとして使用可能になる (※このままではただのクラス)
    ● オブジェクトを+state関数に渡してStateオブジェクトを得る
    ● Stateオブジェクトの内容が更新されると、フレームワーク側でUIの
    再描画が行われる

    View Slide

  10. ToDoアプリ
    class SampleActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
    ToDoApp()
    }
    }
    }
    ● Activity.setContentView() のような役割
    ● この中でコンポーネントを組み立てると、
    フレームワークが最適に描画処理を行う
    ● UIはAndroidCraneView内のCanvasに描画
    される

    View Slide

  11. ToDoAppコンポーネント
    @Composable
    fun ToDoApp() {
    // 20個のサンプルデータ。
    val tasks = (1..20).map {
    Task(id = it, description = "Task No.$it", isCompleted = false)
    }
    MaterialTheme {
    VerticalScroller {
    Surface {
    Column {
    ToDoList(tasks = tasks)
    }
    }
    }
    }
    }
    data class Task(
    val id: Int,
    val description: String,
    val isCompleted: Boolean
    )

    View Slide

  12. ToDoAppコンポーネント
    @Composable
    fun ToDoApp() {
    // 20個のサンプルデータ。
    val tasks = (1..20).map {
    Task(id = it, description = "Task No.$it", isCompleted = false)
    }
    MaterialTheme {
    VerticalScroller {
    Surface {
    Column {
    ToDoList(tasks = tasks)
    }
    }
    }
    }
    }
    ● @Composableアノテーションを付与
    した関数はコンポーネントと認識される
    ● コンポーネントをネストする形で組み立てる
    ので、UIの階層構造がコードで表現される

    View Slide

  13. ToDoAppコンポーネント
    @Composable
    fun ToDoApp() {
    // 20個のサンプルデータ。
    val tasks = (1..20).map {
    Task(id = it, description = "Task No.$it", isCompleted = false)
    }
    MaterialTheme {
    VerticalScroller {
    Surface {
    Column {
    ToDoList(tasks = tasks)
    }
    }
    }
    }
    }
    ● テーマを設定するためのコンポーネント
    ● Material Designのテーマが設定される
    (はず)

    View Slide

  14. ToDoAppコンポーネント
    @Composable
    fun ToDoApp() {
    // 20個のサンプルデータ。
    val tasks = (1..20).map {
    Task(id = it, description = "Task No.$it", isCompleted = false)
    }
    MaterialTheme {
    VerticalScroller {
    Surface {
    Column {
    ToDoList(tasks = tasks)
    }
    }
    }
    }
    }
    ● 子コンポーネントを垂直方向にスクロール可能
    にするためのコンポーネント
    ● これがないと基本的にスクロールできない
    (子コンポーネントがリスト系でも )

    View Slide

  15. ToDoAppコンポーネント
    @Composable
    fun ToDoApp() {
    // 20個のサンプルデータ。
    val tasks = (1..20).map {
    Task(id = it, description = "Task No.$it", isCompleted = false)
    }
    MaterialTheme {
    VerticalScroller {
    Surface {
    Column {
    ToDoList(tasks = tasks)
    }
    }
    }
    }
    }
    ● タップ時のRipple Effectを表現するための
    コンポーネント
    ● これがないとチェックボックスなどの
    タッチフィードバックが表現できない
    (※何なら現状はクラッシュするw )

    View Slide

  16. ToDoAppコンポーネント
    @Composable
    fun ToDoApp() {
    // 20個のサンプルデータ。
    val tasks = (1..20).map {
    Task(id = it, description = "Task No.$it", isCompleted = false)
    }
    MaterialTheme {
    VerticalScroller {
    Surface {
    Column {
    ToDoList(tasks = tasks)
    }
    }
    }
    }
    }
    ● 子コンポーネントを垂直方向にリスト表示する
    ためのコンポーネント (cf. Row)
    ● ここではToDoとDividerを交互にリスト表示
    している
    @Composable
    fun ToDoList(tasks: List) {
    tasks.forEach {
    ToDo(it)
    Divider(
    color = Color.LightGray,
    height = 1.dp
    )
    }
    }

    View Slide

  17. ToDoコンポーネント
    @Composable
    fun ToDo(task: Task) {
    val state = +state {
    TaskModel(description = task.description, isCompleted = task.isCompleted)
    }
    Clickable(onClick = { state.value.isCompleted = !state.value.isCompleted }) {
    Padding(padding = EdgeInsets(all = 20.dp)) {
    Row(crossAxisAlignment = CrossAxisAlignment.Start) {
    Checkbox(
    checked = state.value.isCompleted,
    onCheckedChange = { state.value.isCompleted = it }
    )
    WidthSpacer(width = 10.dp)
    Text(
    text = "${task.description} ${if (state.value.isCompleted) "(completed)" else ""}"
    )
    }
    }
    }
    }
    @Model
    data class TaskModel(
    var description: String,
    var isCompleted: Boolean
    )

    View Slide

  18. ToDoコンポーネント
    @Composable
    fun ToDo(task: Task) {
    val state = +state {
    TaskModel(description = task.description, isCompleted = task.isCompleted)
    }
    Clickable(onClick = { state.value.isCompleted = !state.value.isCompleted }) {
    Padding(padding = EdgeInsets(all = 20.dp)) {
    Row(crossAxisAlignment = CrossAxisAlignment.Start) {
    Checkbox(
    checked = state.value.isCompleted,
    onCheckedChange = { state.value.isCompleted = it }
    )
    WidthSpacer(width = 10.dp)
    Text(
    text = "${task.description} ${if (state.value.isCompleted) "(completed)" else ""}"
    )
    }
    }
    }
    }
    @Model
    data class TaskModel(
    var description: String,
    var isCompleted: Boolean
    )
    ● @Modelアノテーションを付与した
    TaskModelオブジェクトを+state関数に
    渡してStateオブジェクトを得る

    View Slide

  19. ToDoコンポーネント
    @Composable
    fun ToDo(task: Task) {
    val state = +state {
    TaskModel(description = task.description, isCompleted = task.isCompleted)
    }
    Clickable(onClick = { state.value.isCompleted = !state.value.isCompleted }) {
    Padding(padding = EdgeInsets(all = 20.dp)) {
    Row(crossAxisAlignment = CrossAxisAlignment.Start) {
    Checkbox(
    checked = state.value.isCompleted,
    onCheckedChange = { state.value.isCompleted = it }
    )
    WidthSpacer(width = 10.dp)
    Text(
    text = "${task.description} ${if (state.value.isCompleted) "(completed)" else ""}"
    )
    }
    }
    }
    }

    View Slide

  20. ToDoコンポーネント
    @Composable
    fun ToDo(task: Task) {
    val state = +state {
    TaskModel(description = task.description, isCompleted = task.isCompleted)
    }
    Clickable(onClick = { state.value.isCompleted = !state.value.isCompleted }) {
    Padding(padding = EdgeInsets(all = 20.dp)) {
    Row(crossAxisAlignment = CrossAxisAlignment.Start) {
    Checkbox(
    checked = state.value.isCompleted,
    onCheckedChange = { state.value.isCompleted = it }
    )
    WidthSpacer(width = 10.dp)
    Text(
    text = "${task.description} ${if (state.value.isCompleted) "(completed)" else ""}"
    )
    }
    }
    }
    }

    View Slide

  21. ToDoコンポーネント
    @Composable
    fun ToDo(task: Task) {
    val state = +state {
    TaskModel(description = task.description, isCompleted = task.isCompleted)
    }
    Clickable(onClick = { state.value.isCompleted = !state.value.isCompleted }) {
    Padding(padding = EdgeInsets(all = 20.dp)) {
    Row(crossAxisAlignment = CrossAxisAlignment.Start) {
    Checkbox(
    checked = state.value.isCompleted,
    onCheckedChange = { state.value.isCompleted = it }
    )
    WidthSpacer(width = 10.dp)
    Text(
    text = "${task.description} ${if (state.value.isCompleted) "(completed)" else ""}"
    )
    }
    }
    }
    }

    View Slide

  22. ToDoコンポーネント
    @Composable
    fun ToDo(task: Task) {
    val state = +state {
    TaskModel(description = task.description, isCompleted = task.isCompleted)
    }
    Clickable(onClick = { state.value.isCompleted = !state.value.isCompleted }) {
    Padding(padding = EdgeInsets(all = 20.dp)) {
    Row(crossAxisAlignment = CrossAxisAlignment.Start) {
    Checkbox(
    checked = state.value.isCompleted,
    onCheckedChange = { state.value.isCompleted = it }
    )
    WidthSpacer(width = 10.dp)
    Text(
    text = "${task.description} ${if (state.value.isCompleted) "(completed)" else ""}"
    )
    }
    }
    }
    }

    View Slide

  23. ToDoコンポーネント
    @Composable
    fun ToDo(task: Task) {
    val state = +state {
    TaskModel(description = task.description, isCompleted = task.isCompleted)
    }
    Clickable(onClick = { state.value.isCompleted = !state.value.isCompleted }) {
    Padding(padding = EdgeInsets(all = 20.dp)) {
    Row(crossAxisAlignment = CrossAxisAlignment.Start) {
    Checkbox(
    checked = state.value.isCompleted,
    onCheckedChange = { state.value.isCompleted = it }
    )
    WidthSpacer(width = 10.dp)
    Text(
    text = "${task.description} ${if (state.value.isCompleted) "(completed)" else ""}"
    )
    }
    }
    }
    }

    View Slide

  24. ToDoコンポーネント
    @Composable
    fun ToDo(task: Task) {
    val state = +state {
    TaskModel(description = task.description, isCompleted = task.isCompleted)
    }
    Clickable(onClick = { state.value.isCompleted = !state.value.isCompleted }) {
    Padding(padding = EdgeInsets(all = 20.dp)) {
    Row(crossAxisAlignment = CrossAxisAlignment.Start) {
    Checkbox(
    checked = state.value.isCompleted,
    onCheckedChange = { state.value.isCompleted = it }
    )
    WidthSpacer(width = 10.dp)
    Text(
    text = "${task.description} ${if (state.value.isCompleted) "(completed)" else ""}"
    )
    }
    }
    }
    }
    ● stateのプロパティを更新することにより、フレー
    ムワーク側でUIの再描画が行われる
    状態をトグルする
    チェックボックスの状態変
    更を反映する

    View Slide

  25. Pros.
    ● ソースコードからUIが直感的に理解できる
    ● kt/javaファイル、layout.xml、attrs.xml、drawable.xmlに
    別個に書いていた内容を集約できる
    Cons.
    ● ネスト地獄
    ● Paddingとかの設定までコンポーネントで行うのは個人的には
    つらい (例えば、Textのプロパティとして渡したい)
    所感

    View Slide

  26. まだPre-alpha版なので…

    View Slide

  27. Thank you!
    \ Follow me on Twitter /

    View Slide