$30 off During Our Annual Pro Sale. View Details »

持続的なアプリ開発のためのDXを支える技術

 持続的なアプリ開発のためのDXを支える技術

Keishin Yokomaku

February 21, 2020
Tweet

More Decks by Keishin Yokomaku

Other Decks in Technology

Transcript

  1. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷ
    DXΛࢧ͑Δٕज़
    KeithYokoma (Keishin Yokomaku) /

    DroidKaigi 2020

    View Slide

  2. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    About Me
    ▸ Keishin Yokomaku
    ▸ @KeithYokoma: GitHub / Twitter / Qiita / Stack Overflow
    ▸ Merpay, Inc. / Engineer
    ▸ Fun: Gymnastics / Cycling / Photography / Motorsport / Camping
    DroidKaigi 2020
    2

    View Slide

  3. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ͜ͷηογϣϯͷ໨త: Objectives
    ▸ ϞόΠϧΞϓϦͷ։ൃϓϩηεΛ࣋ଓతʹૉૣ͘ճ͚ͭͮ͠ΒΕΔΑ͏ʹͳΔ
    ▸ ΤϯδχΞϦϯάνʔϜͱͯ͠ɺΑ͍ DX Λ֫ಘ͢ΔͨΊɺ

    ։ൃʹணख͢Δલ͔Β࣮ࡍʹϦϦʔεͯ͠ӡ༻͍ͯ͘͠·Ͱͷؒʹɺ

    ͲΜͳ͜ͱʹ஫໨ͯ͠ࢪࡦʹऔΓ૊ΉͱΑ͍͔ɺࢦ਑Λࣔ͠·͢
    DroidKaigi 2020
    3

    View Slide

  4. Developer Experience
    ։ൃମݧ
    DroidKaigi 2020

    View Slide

  5. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ͳͥ DX ͕ॏཁ͔: Why DX matters?
    ▸ ϓϩμΫτ͸೔ʑ੒௕Λଓ͚Δ
    ▸ ৽ػೳͷ௥Ճ
    ▸ طଘػೳͷվम
    ▸ όάमਖ਼
    ▸ ͳͲ
    DroidKaigi 2020
    5

    View Slide

  6. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ͳͥ DX ͕ॏཁ͔: Why DX matters?
    ▸ ιʔείʔυ΋ຖ೔มԽΛ͠ଓ͚Δ
    ▸ ৽͍͠Ϋϥεɾϝιουͷ௥Ճ
    ▸ طଘΫϥεɾϝιουͷৼΔ෣͍ͷมߋ
    ▸ ϥΠϒϥϦͷߋ৽
    ▸ ͳͲ
    DroidKaigi 2020
    6

    View Slide

  7. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ͳͥ DX ͕ॏཁ͔: Why DX matters?
    ▸ ୹ظؒͷΠςϨʔγϣϯΛܧଓ͠ɺϓϩμΫτΛ੒௕ͤ͞Δϓϩηε͕ඞཁ
    ▸ 1 ~ 2 िؒ͝ͱͷϦϦʔεͰԾઆݕূΛͲΜͲΜճ͍ͯ͘͠
    ▸ ૉૣ͍ػೳ࣮૷ʢࡉ͔͘࡞Γ্͍͛ͯ͘ϓϩηεͷ܁Γฦ͠ʣ
    ▸ ૉૣ͍ৼΓฦΓʢࡉֶ͔͘ͼΛൃݟ͠దԠ͍ͯ͘͠ϓϩηεͷ܁Γฦ͠ʣ
    DroidKaigi 2020
    7

    View Slide

  8. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ͳͥ DX ͕ॏཁ͔: Why DX matters?
    ▸ ୹ظؒͷΠςϨʔγϣϯΛܧଓ͠ɺϓϩμΫτΛ੒௕ͤ͞Δϓϩηε͕ඞཁ
    ▸ 1 ~ 2 िؒ͝ͱͷϦϦʔεͰԾઆݕূΛͲΜͲΜճ͍ͯ͘͠
    ▸ ૉૣ͍ػೳ࣮૷ʢࡉ͔͘࡞Γ্͍͛ͯ͘ϓϩηεͷ܁Γฦ͠ʣ
    ▸ ૉૣ͍ৼΓฦΓʢࡉֶ͔͘ͼΛൃݟ͠దԠ͍ͯ͘͠ϓϩηεͷ܁Γฦ͠ʣ
    ▸ Α͍ DX Λ࡞Δ͜ͱͰɺ͜ͷϓϩηεΛ͏·͘ճͤΔΑ͏ʹͳΔ
    ▸ DX ͸ϓϩμΫτͷ੒௕ʹඞཁෆՄܽ
    DroidKaigi 2020
    8

    View Slide

  9. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    DroidKaigi 2020
    9

    View Slide

  10. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    Agenda
    ▸ ࣄલ४උ
    ▸ ઃܭ
    ▸ ։ൃϓϩηε
    ▸ ϦϦʔε
    DroidKaigi 2020
    10

    View Slide

  11. Preparation

    ࣄલ४උ
    DroidKaigi 2020

    View Slide

  12. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ࣄલ४උ: Preparation
    ▸ ࣄલʹνʔϜͰ࿩͠߹͓ͬͯ͘ͱΑ͍͜ͱ
    ▸ ίʔσΟϯάن໿
    ▸ ϒϥϯνઓུ
    DroidKaigi 2020
    12

    View Slide

  13. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ίʔσΟϯάن໿: Coding standards
    ▸ ίʔυͷॻ͖ํ (ελΠϧ) ʹ͍ͭͯೝࣝΛἧ͑Δ
    ▸ εϖʔεΠϯσϯτ vs λϒΠϯσϯτ
    ▸ ࠷ऴߦʹվߦΛ͍ΕΔ͔Ͳ͏͔
    ▸ Ұߦͷ௕͞
    ▸ etc…
    DroidKaigi 2020
    13

    View Slide

  14. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ίʔσΟϯάن໿: Coding standards
    ▸ ίʔσΟϯάن໿ΛϦϙδτϦͰڞ༗͍ͨ͠
    ▸ .editorconfig
    ▸ IDE ʹίʔσΟϯάن໿ʹैͬͨϑΥʔϚοτΛͤ͞Δ
    ▸ AndroidStudio/IntelliJ IDEA ͳΒ௥Ճͷ Plugin ͳ͠Ͱ࢖͑Δ
    DroidKaigi 2020
    14

    View Slide

  15. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    .editorconfig ͷྫ: .editorconfig example
    root = true

    [*] "// apply the following styles to all files

    indent_style = space

    indent_size = 2

    end_of_line = lf

    charset = utf-8

    trim_trailing_whitespace = true

    insert_final_newline = true

    [*.{java,kt,kts,xml}] "// only apply to java/kt/kts/xml files

    max_line_length = 140

    [*.md] "// only apply to markdown files

    trim_trailing_whitespace = false

    DroidKaigi 2020

    View Slide

  16. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ϒϥϯνઓུ: Branch strategy
    ▸ ϒϥϯν͝ͱʹ໾ׂΛܾΊͯӡ༻͢ΔͨΊͷϧʔϧͮ͘Γ
    ▸ Git flow
    ▸ GitHub flow
    DroidKaigi 2020
    16

    View Slide

  17. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    Git flow
    ▸ master
    ▸ ϦϦʔε͝ͱʹ develop ΛϚʔδ͠ tag ΛଧͭͨΊͷϒϥϯν
    ▸ develop
    ▸ ීஈͷ։ൃʹ͓͍ͯɺϨϏϡʔࡁΈͷࠩ෼ΛऔΓࠐΈଓ͚Δϒϥϯν
    ▸ release/hotfix branches
    ▸ ϨϏϡʔࡁΈͷόάमਖ਼ࠩ෼ΛऔΓࠐΉϒϥϯν
    DroidKaigi 2020
    17

    View Slide

  18. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    Git flow - Overview
    DroidKaigi 2020
    18
    master
    hotfix!/*
    release!/*
    bugfix"/*
    feature"/*
    develop
    v1.1.0 v1.1.1

    View Slide

  19. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    Git flow - Feature development
    DroidKaigi 2020
    19
    master
    hotfix!/*
    release!/*
    bugfix"/*
    feature"/*
    develop
    Feature A Feature B

    View Slide

  20. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    Git flow - Feature verification and release for v1.1.0
    DroidKaigi 2020
    20
    master
    hotfix!/*
    release!/*
    bugfix"/*
    feature"/*
    develop
    v1.1.0
    Bugfix Bugfix Bugfix

    View Slide

  21. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    Git flow
    DroidKaigi 2020
    21
    master
    hotfix!/*
    release!/*
    bugfix"/*
    feature"/*
    develop
    v1.1.0 v1.1.1
    Bugfix

    View Slide

  22. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    GitHub flow
    ▸ master
    ▸ ৗʹϦϦʔεՄೳͳࠩ෼Λอ͍࣋ͯ͠Δϒϥϯν
    DroidKaigi 2020
    22

    View Slide

  23. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    GitHub flow
    DroidKaigi 2020
    23
    master
    v1.1.0 v1.3.0
    feature"/*
    Feature A Feature A
    v1.2.0
    Bugfix
    v1.2.1
    Feature C

    View Slide

  24. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ϒϥϯνઓུͷൺֱ: Comparison between Git flow and GitHub flow
    ▸ Git flow
    ▸ ϦϦʔεʹ޲͚ͨࠩ෼؅ཧͷͨΊͷϒϥϯνͷ෼͚ํΛ͢Δ
    ▸ ωΠςΟϒΞϓϦͱ૬ੑ͕͍͍
    ▸ GitHub flow
    ▸ ΞϓϦέʔγϣϯΛࡉ͔͘σϓϩΠ͢Δ͜ͱʹϑΥʔΧε͍ͯ͠Δ
    ▸ සൟʹσϓϩΠ͢ΔαʔόʔΞϓϦέʔγϣϯͱ૬ੑ͕͍͍
    DroidKaigi 2020
    24

    View Slide

  25. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ϒϥϯνઓུͷ࠷దԽ: Optimize branch strategy
    ▸ ࣗ෼ͨͪͷϫʔΫϑϩʔʹ࠷దԽͯ͠ OK
    ▸ Git flow ͸΍Δ͜ͱ͕ଟͯ͘؅ཧ͕໘౗ʹͳΓ͕ͪ
    ▸ GitHub flow Ͱ͸ QA ϑΣʔζͷͱ͖ɺ

    ฏߦͯ͠ػೳ։ൃϒϥϯνΛϚʔδͮ͠Β͘ͳΔ
    ➡ ྆ऀͷؒͷࢠͷΑ͏ͳઓུΛߟ͑ͯΈΔ
    DroidKaigi 2020
    25

    View Slide

  26. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    Combination of Git flow and GitHub flow
    DroidKaigi 2020
    26
    master
    feature"/*
    release!/*
    Feature A Feature B
    bugfix"/*
    Bugfix Bugfix
    hotfix!/*
    v1.1.0
    Bugfix
    v1.1.1

    View Slide

  27. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    Combination of Git flow and GitHub flow
    DroidKaigi 2020
    27
    master
    feature"/*
    release!/*
    Feature A Feature B
    bugfix"/*
    hotfix!/*

    View Slide

  28. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    Combination of Git flow and GitHub flow
    DroidKaigi 2020
    28
    master
    feature"/*
    release!/*
    Feature A Feature B
    bugfix"/*
    Bugfix Bugfix
    v1.1.0
    hotfix!/*

    View Slide

  29. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    Combination of Git flow and GitHub flow
    DroidKaigi 2020
    29
    master
    feature"/*
    release!/*
    Feature A Feature B
    bugfix"/*
    Bugfix Bugfix
    hotfix!/*
    v1.1.0
    Bugfix
    v1.1.1

    View Slide

  30. Architecture
    ઃܭ
    DroidKaigi 2020

    View Slide

  31. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ઃܭ: Architecture
    ▸ ઃܭΛߟ͑Δͱ͖ͷϙΠϯτΛ཈͑Δ
    ▸ ઃܭͷ໨త
    ▸ ϢχοτςετͰ͔֬ΊΔ͜ͱ͕Β
    ▸ ઃܭΛॿ͚ΔϞδϡʔϧߏ੒
    DroidKaigi 2020
    31

    View Slide

  32. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ઃܭ: Architecture
    ▸ ͲͷύλʔϯΛ࠾༻͢Δͷ͔
    ▸ MVVM
    ▸ MVP
    ▸ MVC
    ▸ MVI
    DroidKaigi 2020
    32

    View Slide

  33. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ઃܭ: Architecture
    ▸ ͲͷύλʔϯΛ࠾༻͢Δͷ͔
    ▸ MVVM
    ▸ MVP
    ▸ MVC
    ▸ MVI
    ▸ ͍ͬͺ͍͋Δ
    DroidKaigi 2020
    33

    View Slide

  34. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ઃܭ: Architecture
    ▸ ઃܭΛ͢Δ໨త
    ▸ Ϋϥε΍ϝιουʹద੾ͳ໋໊Λͯ͠ɺؔ৺Λ෼͔Γ΍͘͢͢Δ
    ▸ ؔ৺Λ෼཭ͯ͠ɺϝϯςφϯε΍ςετΛ͠΍͘͢͢Δ
    DroidKaigi 2020
    34

    View Slide

  35. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ઃܭ: Architecture
    ▸ ϞόΠϧΞϓϦͷઃܭͰϑΥʔΧε͍ͨ͜͠ͱ
    ▸ API௨৴΍ϏδωεϩδοΫͳͲΛը໘ͷ࣮૷͔Β͏·͘෼཭͍ͨ͠
    ▸ ը໘͕΋ͭঢ়ଶͱͦͷભҠͷํ๏Λ͏·͘දݱ͍ͨ͠
    ▸ ঢ়ଶʹԠͨ͡ UI ͷߋ৽ϩδοΫΛ੾Γ཭͍ͨ͠
    ▸ ͜ΕΒ͕Ϣχοτςετ͠΍͍͢ܗͰ࣮ݱͰ͖Δ͜ͱ
    ▸ ඞཁ࠷௿ݶͷϞοΫΛ४උ͢Δ͚ͩͰࡁ·͍ͤͨ
    DroidKaigi 2020
    35

    View Slide

  36. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ઃܭ: Architecture
    ▸ ͏·͘ઃܭύλʔϯΛ࢖͍͜ͳ͢ (ྫ)
    ▸ UIɺϏδωεϩδοΫɺAPI ௨৴ͳͲϨΠϠʔΛ͏·͘෼཭͍ͨ͠
    ➡ ϨΠϠʔυΞʔΩςΫνϟɺΫϦʔϯΞʔΩςΫνϟ
    ▸ UI ͕΋ͭঢ়ଶͱͦͷભҠΛ͏·͘දݱ͍ͨ͠
    ➡ ReduxɺFluxɺ͋Δ͍͸ LiveData ʹΑΔঢ়ଶભҠͷ௨஌
    ▸ UI ͷૢ࡞Λೖྗͱͯ͠ϏδωεϩδοΫΛಈ͔͠ɺͦͷ݁ՌΛ͏·͘ UI ʹ൓ө͍ͨ͠
    ➡ MVVMɺDataBindingɺ͋Δ͍͸؆୯ͳҕৡύλʔϯ
    DroidKaigi 2020
    36

    View Slide

  37. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    UI ͷঢ়ଶͱͦͷભҠΛ͏·͘දݱ͢Δ: Implement UI state machine
    ▸ ࢓૊Έͱ࣮ͯ͠૷ʹམͱ͠ࠐΜͩ΋ͷ
    ▸ ReduxɺFlux
    ▸ ঢ়ଶΛද͢ΦϒδΣΫτ: State
    ▸ ঢ়ଶભҠͷ͖͔͚ͬΛͭ͘ΔΦϒδΣΫτ: Action
    ▸ ঢ়ଶભҠͷৄࡉΛ࣮૷͢Δ: Reducer (Redux)ɺStore (Flux)
    DroidKaigi 2020
    37

    View Slide

  38. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    UI ͷঢ়ଶͱͦͷભҠΛ͏·͘දݱ͢Δ: Implement UI state machine
    ▸ Redux ΍ Flux ͷߟ͑ํΛ LiveData + ViewModel Ͱ୅༻ͯ͠ΈΔ
    ▸ ঢ়ଶΛද͢ΦϒδΣΫτ: State
    ▸ LiveData Ͱ State ͷมߋΛ௨஌
    ▸ ViewModel ͸ State ͷભҠํ๏Λఆٛͨ͠ϝιουΛ࣋ͭ
    DroidKaigi 2020
    38

    View Slide

  39. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    UI ͷঢ়ଶͱͦͷભҠΛ͏·͘දݱ͢Δ: Implement UI state machine
    ▸ ঢ়ଶΛද͢ΦϒδΣΫτͷઃܭ
    ▸ ඇಉظॲཧͷॲཧதɾ੒ޭɾࣦഊͷදݱ
    ▸ Either Λ΋͏গ֦͠ுͨ͠ sealed class ʹΑΔදݱ
    ▸ ॳظঢ়ଶɾॲཧதɾ੒ޭɾࣦഊΛද͢
    ▸ RemoteDataK / Kotlin ͷ sealed class Λ࢖͍͜ͳ͢ by kikuchy
    DroidKaigi 2020
    39

    View Slide

  40. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    UI ͷঢ়ଶͱͦͷભҠΛ͏·͘දݱ͢Δ: Implement UI state machine
    ▸ ঢ়ଶΛද͢ΦϒδΣΫτͷઃܭ
    ▸ UI Ͱඞཁͳσʔλܕ΁ͷม׵
    ▸ API Ϩεϙϯεͷܕ͕ͦͷ··ը໘࣮૷ʹ౰ͯ͸·Δͱ͸ݶΒͳ͍
    ▸ ϨΠϠʹ߹ΘͤͨܕͷίϯόʔτΛͯ͋͛͠Δ
    ▸ e.g. ෳ਺ͷ API ϨεϙϯεΛ૊Έ߹Θͤͯ UI ʹ൓ө͢Δ
    ➡ ෳ਺ͷ API ϨεϙϯεΛ߹੒ͨ͠ܕΛ࡞Δ
    DroidKaigi 2020
    40

    View Slide

  41. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    UI ͷঢ়ଶͱͦͷભҠΛ͏·͘දݱ͢Δ: Implement UI state machine
    ▸ ঢ়ଶΛද͢ΦϒδΣΫτͷઃܭ
    ▸ ྫ֎ͷऔΓѻ͍
    ▸ ϓϩάϥϛϯάͷϛεʹΑΔ΋ͷ: ແཧʹ catch ͠ͳ͍
    ▸ I/O ͷࣦഊ: ࣦഊͨ͠ཧ༝Λઆ໌͢ΔΦϒδΣΫτʹม׵
    ▸ see also: ந৅֓೦ʹదͨ͠ྫ֎Λεϩʔ͢Δ

    (Effective Java 3rd Edition: Item 73)
    DroidKaigi 2020
    41

    View Slide

  42. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    UI ͷঢ়ଶͱͦͷભҠΛ͏·͘දݱ͢Δ: Implement UI state machine
    data class SampleViewState(

    "// RemoteData: sealed type describing data loading state

    "// Initial/Loading/Success/Failure

    val apiData: RemoteData = Initial

    "// ……

    )

    View Slide

  43. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    UI ͷঢ়ଶͱͦͷભҠΛ͏·͘දݱ͢Δ: Implement UI state machine
    class SampleViewModel(

    private val dataSource: SampleDataSource

    ) : ViewModel() {

    "// never allow others to mutate the state

    private val viewStatePublisher: MutableLiveData =

    MutableLiveData(SampleViewState())

    val viewState: LiveData = viewStatePublisher

    fun loadData() {

    "// …… implement state changes

    "// …… start loading, subscribe result from data source, emit success or failure

    }

    }

    View Slide

  44. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    UI ͷঢ়ଶͱͦͷભҠΛ͏·͘දݱ͢Δ: Implement UI state machine
    class SampleViewModel(private val dataSource: SampleDataSource) : ViewModel() {

    private val viewStatePublisher: MutableLiveData =

    MutableLiveData(SampleViewState())

    fun loadData() {

    viewStatePublisher.value"?.let { state "->

    "// notify apiData becomes loading

    viewStatePublisher.value = state.copy(apiData = Loading())

    }

    }

    }

    View Slide

  45. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    UI ͷঢ়ଶͱͦͷભҠΛ͏·͘දݱ͢Δ: Implement UI state machine
    class SampleViewModel(private val dataSource: SampleDataSource) : ViewModel() {

    fun loadData() {

    "// ……

    dataSource.loadApiData()

    .observeOn(AndroidSchedulers.mainThread())

    .subscribe({ data "->

    viewStatePublisher.value"?.let { state "->

    "// notify apiData successfully loaded

    viewStatePublisher.value = state.copy(apiData = Success(data))

    }

    }, { exception "->

    viewStatePublisher.value"?.let { state "->

    "// notify apiData loading failed

    viewStatePublisher.value = state.copy(apiData = Failure(exception))

    }

    })

    }

    }

    View Slide

  46. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    UI ͷঢ়ଶͱͦͷભҠΛ͏·͘දݱ͢Δ: Implement UI state machine
    class SampleFragment : Fragment() {

    private lateinit var viewModel: SampleViewModel

    override fun onCreate(savedInstanceState: Bundle?) {

    "// ……

    viewModel.loadData()

    viewModel.viewState.observe(this, Observer { state "->

    when (it) {

    is Loading "-> "// show loading view

    is Success "-> "// apply data to UI

    is Failure "-> "// show error message

    }

    })

    }

    }

    View Slide

  47. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ςετ: Test
    ▸ State ͷঢ়ଶભҠΛςετ͢Δ৔߹
    ▸ ͋Δೖྗʹର͠ɺظ଴ͨ͠௨Γͷ State ͕ग़ྗ͞ΕΔ͜ͱ
    ▸ State ͷมԽͷॱ൪͕कΒΕ͍ͯΔ͜ͱ
    ▸ Initial → Loading → Success or Failure
    DroidKaigi 2020
    47

    View Slide

  48. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    UI ͷঢ়ଶભҠͷςετ: Unit Test for UI state machine
    class SampleViewModel(private val dataSource: SampleDataSource) : ViewModel() {

    private val viewStatePublisher: MutableLiveData = MutableLiveData(SampleViewState())

    val viewState: LiveData = viewStatePublisher

    fun startLoading() {

    viewStatePublisher.value"?.let { state "->

    "// notify apiData becomes loading

    viewStatePublisher.value = state.copy(apiData = Loading())

    }

    dataSource.loadApiData()

    .observeOn(AndroidSchedulers.mainThread())

    .subscribe({ data "->

    viewStatePublisher.value"?.let { state "->

    "// notify apiData successfully loaded

    viewStatePublisher.value = state.copy(apiData = Success(data))

    }

    }, { exception "->

    viewStatePublisher.value"?.let { state "->

    "// notify apiData loading failed

    viewStatePublisher.value = state.copy(apiData = Failure(exception))

    }

    })

    }

    }

    View Slide

  49. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    UI ͷঢ়ଶભҠͷςετ: Unit Test for UI state machine
    class SampleViewModel(private val dataSource: SampleDataSource) : ViewModel() {

    private val viewStatePublisher: MutableLiveData = MutableLiveData(SampleViewState())

    val viewState: LiveData = viewStatePublisher

    fun startLoading() {

    viewStatePublisher.value"?.let { state "->

    "// notify apiData becomes loading

    viewStatePublisher.value = state.copy(apiData = Loading())

    }

    dataSource.loadApiData()

    .observeOn(AndroidSchedulers.mainThread())

    .subscribe({ data "->

    viewStatePublisher.value"?.let { state "->

    "// notify apiData successfully loaded

    viewStatePublisher.value = state.copy(apiData = Success(data))

    }

    }, { exception "->

    viewStatePublisher.value"?.let { state "->

    "// notify apiData loading failed

    viewStatePublisher.value = state.copy(apiData = Failure(exception))

    }

    })

    }

    }
    Observed as

    1st value

    View Slide

  50. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    UI ͷঢ়ଶભҠͷςετ: Unit Test for UI state machine
    class SampleViewModel(private val dataSource: SampleDataSource) : ViewModel() {

    private val viewStatePublisher: MutableLiveData = MutableLiveData(SampleViewState())

    val viewState: LiveData = viewStatePublisher

    fun startLoading() {

    viewStatePublisher.value"?.let { state "->

    "// notify apiData becomes loading

    viewStatePublisher.value = state.copy(apiData = Loading())

    }

    dataSource.loadApiData()

    .observeOn(AndroidSchedulers.mainThread())

    .subscribe({ data "->

    viewStatePublisher.value"?.let { state "->

    "// notify apiData successfully loaded

    viewStatePublisher.value = state.copy(apiData = Success(data))

    }

    }, { exception "->

    viewStatePublisher.value"?.let { state "->

    "// notify apiData loading failed

    viewStatePublisher.value = state.copy(apiData = Failure(exception))

    }

    })

    }

    }
    Observed as

    2nd value

    View Slide

  51. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    UI ͷঢ়ଶભҠͷςετ: Unit Test for UI state machine
    class SampleViewModel(private val dataSource: SampleDataSource) : ViewModel() {

    private val viewStatePublisher: MutableLiveData = MutableLiveData(SampleViewState())

    val viewState: LiveData = viewStatePublisher

    fun startLoading() {

    viewStatePublisher.value"?.let { state "->

    "// notify apiData becomes loading

    viewStatePublisher.value = state.copy(apiData = Loading())

    }

    dataSource.loadApiData()

    .observeOn(AndroidSchedulers.mainThread())

    .subscribe({ data "->

    viewStatePublisher.value"?.let { state "->

    "// notify apiData successfully loaded

    viewStatePublisher.value = state.copy(apiData = Success(data))

    }

    }, { exception "->

    viewStatePublisher.value"?.let { state "->

    "// notify apiData loading failed

    viewStatePublisher.value = state.copy(apiData = Failure(exception))

    }

    })

    }

    }
    Observed as

    3rd value if success

    View Slide

  52. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    UI ͷঢ়ଶભҠͷςετ: Unit Test for UI state machine
    class SampleViewModel(private val dataSource: SampleDataSource) : ViewModel() {

    private val viewStatePublisher: MutableLiveData = MutableLiveData(SampleViewState())

    val viewState: LiveData = viewStatePublisher

    fun startLoading() {

    viewStatePublisher.value"?.let { state "->

    "// notify apiData becomes loading

    viewStatePublisher.value = state.copy(apiData = Loading())

    }

    dataSource.loadApiData()

    .observeOn(AndroidSchedulers.mainThread())

    .subscribe({ data "->

    viewStatePublisher.value"?.let { state "->

    "// notify apiData successfully loaded

    viewStatePublisher.value = state.copy(apiData = Success(data))

    }

    }, { exception "->

    viewStatePublisher.value"?.let { state "->

    "// notify apiData loading failed

    viewStatePublisher.value = state.copy(apiData = Failure(exception))

    }

    })

    }

    }
    Observed as

    3rd value if error

    View Slide

  53. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    UI ͷঢ়ଶભҠͷςετ: Unit Test for UI state machine
    class SampleViewModelTest : Spek({

    beforeEachTest {

    RxAndroidPlugins.setMainThreadSchedulerHandler { Scheduler.trampoline() }

    ArchTaskExecutor.getInstance().setDelegate(TestExecutor())

    }

    afterEachTest {

    RxAndroidPlugins.reset()

    ArchTaskExecutor.getInstance().setDelegate(null)

    }

    })

    class TestExecutor : TaskExecutor() {

    override fun executeOnDiskIO(runnable: Runnable) { runnable.run() }

    override fun isMainThread(): Boolean = true

    override fun postToMainThread(runnable: Runnable) { runnable.run() }

    }

    View Slide

  54. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    UI ͷঢ়ଶભҠͷςετ: Unit Test for UI state machine
    class SampleViewModelTest : Spek({

    Feature("SampleViewModel#startLoading") {

    val dataSource: SampleDataSource by memoized(CachingMode.EACH_GROUP) {

    mockk(relaxed = true)

    }

    Scenario("Load data successfully") {

    "// Test cases verifying state changes in successful scenario

    }

    Scenario("Load data unsuccessfully") {

    "// Test cases verifying state changes in failure scenario

    }

    }

    })

    View Slide

  55. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    UI ͷঢ়ଶભҠͷςετ: Unit Test for UI state machine
    class SampleViewModelTest : Spek({

    Scenario("Load data successfully") {

    lateinit var viewModel: SampleViewModel

    lateinit var observer: Observer

    lateinit var changedStateSlot: CapturingSlot

    Given("ViewModel with initial state and state observer") {

    changedStateSlot = slot()

    observer = mockk(relaxed = true) {

    every { onChanged(capture(changedStateSlot)) } just Runs

    }

    every { dataSource.loadApiData() } returns Observable.just("foo")

    viewModel = NotificationViewModel(dataSource)

    }

    }

    })

    View Slide

  56. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    UI ͷঢ়ଶભҠͷςετ: Unit Test for UI state machine
    class SampleViewModelTest : Spek({

    When("Start observing states") {

    viewModel.viewState.observeForever(observer)

    }

    And("Start loading") {

    viewModel.startLoading()

    }

    Then("State changes observed in the specified order") {

    verifyOrder {

    observer.onChanged(eq(SampleViewState(apiData = Initial)))

    observer.onChanged(eq(SampleViewState(apiData = Loading())))

    observer.onChanged(eq(SampleViewState(apiData = Success("foo"))))

    }

    }

    })

    View Slide

  57. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    UI ͷঢ়ଶભҠͷςετ: Unit Test for UI state machine
    class SampleViewModelTest : Spek({

    When("Start observing states") {

    viewModel.viewState.observeForever(observer)

    }

    And("Start loading") {

    viewModel.startLoading()

    }

    Then("State changes observed in the specified order") {

    verifyOrder {

    observer.onChanged(eq(SampleViewState(apiData = Initial)))

    observer.onChanged(eq(SampleViewState(apiData = Loading())))

    observer.onChanged(eq(SampleViewState(apiData = Success("foo"))))

    }

    }

    })

    View Slide

  58. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    Ϟδϡʔϧߏ੒ͱ໋໊نଇ: Module structure and naming convention
    ▸ γϯάϧϞδϡʔϧ
    ▸ ϚϧνϞδϡʔϧ
    ▸ Dynamic Feature Modules
    DroidKaigi 2020
    58

    View Slide

  59. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    γϯάϧϞδϡʔϧ: Single module
    ▸ apk Λੜ੒͢ΔϞδϡʔϧʹ͢΂͕ͯ͋Δ
    ▸ Pros
    ▸ ߏ੒ͷ͜ͱͰ೰·ͳͯ͘ࡁΉ
    ▸ Cons
    ▸ Ϗϧυ଎౓Λ্͛ͨ͘ͳͬͨͱ͖ʹɺGradle ͷ͘͠ΈͷԸܙΛड͚ͮΒ͍
    ▸ Ͳͷύοέʔδ΋༰қʹࢀরՄೳͳͨΊɺҙਤ͠ͳ͍ґଘؔ܎Λ࡞Γ΍͍͢
    DroidKaigi 2020
    59

    View Slide

  60. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ϚϧνϞδϡʔϧ: Multiple modules
    ▸ ϞδϡʔϧΛෳ਺ʹ෼཭͠ɺapk Λੜ੒͢ΔϞδϡʔϧ͕͢΂ͯΛू໿͢Δ
    ▸ Pros
    ▸ Ϟδϡʔϧͷ໋໊ʹΑͬͯ໾ׂΛ໌ࣔͰ͖Δ
    ▸ Gradle ͷ࢓૊ΈͰϏϧυ଎౓Λ্͛΍͍͢
    ▸ Cons
    ▸ Ϟδϡʔϧͷ෼཭ํ๏ʹڞ௨ೝ͕ࣝඞཁ
    DroidKaigi 2020
    60

    View Slide

  61. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    μΠφϛοΫϑΟʔνϟʔϞδϡʔϧ: Dynamic Feature Modules
    ▸ Dynamic Delivery Λ͢ΔͳΒඞਢ
    ▸ Pros
    ▸ ϚϧνϞδϡʔϧ͕ڧ੍ʹͳΔͷͰɺಉ͡ϝϦοτΛಘΒΕΔ
    ▸ Play Store Ͱ഑෍͢ΔΞϓϦͷαΠζΛখ͘͞Ͱ͖Δ
    ▸ Cons
    ▸ Ϟδϡʔϧͷ෼཭ํ๏ʹڞ௨ೝ͕ࣝඞཁ
    DroidKaigi 2020
    61

    View Slide

  62. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    Ϟδϡʔϧͷ෼ׂํ๏: Modularization strategy
    ▸ Ͳ͏͍͏୯ҐͰϞδϡʔϧΛͭ͘Δͷ͔ܾΊ͓ͯ͘
    ▸ ϨΠϠʔυΞʔΩςΫνϟͷ૚Ͱ෼ׂ
    ▸ ػೳ͝ͱʹ෼ׂ
    ▸ ͜ΕΒͷ૊Έ߹Θͤ
    DroidKaigi 2020
    62

    View Slide

  63. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    Ϟδϡʔϧͷ໋໊: Naming convention for module
    ▸ Ϟδϡʔϧ෼ׂͷ୯ҐʹԠ໋໊ͨ͡نଇΛ࡞͓ͬͯ͘
    ▸ ΞϓϦέʔγϣϯຊମ: app
    ▸ ֤छػೳͷ UI ࣮૷: feature_**
    ▸ Ϣʔεέʔε: usecase_**
    ▸ σʔλΞΫηε: repository_**
    ▸ ڞ௨ॲཧ: common_**
    DroidKaigi 2020
    63

    View Slide

  64. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    Ϟδϡʔϧͷґଘؔ܎: Dependencies between modules
    DroidKaigi 2020
    64
    feature_a
    repository_a
    usecase_a
    feature_b
    usecase_b
    repository_b
    feature_b
    usecase_c
    repository_c

    View Slide

  65. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    Ϟδϡʔϧͷґଘؔ܎: Dependencies between modules
    DroidKaigi 2020
    65
    feature_a
    repository_a
    usecase_a
    feature_b
    usecase_b
    repository_b
    feature_b
    usecase_c
    repository_c

    View Slide

  66. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    Ϟδϡʔϧͷґଘؔ܎: Dependencies between modules
    DroidKaigi 2020
    66
    feature_a
    repository_a
    usecase_a
    feature_b
    usecase_b
    repository_b
    feature_b
    usecase_c
    repository_c

    View Slide

  67. Development Process
    ։ൃϓϩηε
    DroidKaigi 2020

    View Slide

  68. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ։ൃϓϩηε: Development Process
    ▸ ೔ʑͷ։ൃϓϩηεΛεϜʔζʹճ͠ଓ͚ΔͨΊͷऔΓ૊Έ
    ▸ ຊ౰ʹਓͷྗΛඞཁͱ͢Δ৔ॴʹਓ͕ूதͯ͠औΓ૊ΊΔΑ͏ʹ͢Δ
    ▸ খ͞ͳࣦഊʹૉૣ͘ؾ෇͚Δ࢓૊ΈΛ࡞Δ
    ▸ ٕज़తͳϑΟʔυόοΫϧʔϓΛ࡞Δ
    ▸ ࣋ଓతʹऔΓ૊ΈΛճ͍ͯ͘͠࢓૊ΈΛ࡞Δ
    ▸ ࣗಈԽʂ
    DroidKaigi 2020
    68

    View Slide

  69. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ࣗಈԽ: Automation
    ▸ ࣗಈԽ͢Δͱ͖ʹߟྀ͢Δ͜ͱ
    ▸ ඞཁͳ͜ͱΛɺඞཁͳ͚࣮ͩߦ͢Δ͜ͱ
    ▸ Ϗϧυͷ੒Ռ෺Λ୭Ͱ΋ΞΫηεͰ͖Δ৔ॴʹอଘ͓ͯ͘͜͠ͱ
    ▸ ϓϩδΣΫτͷ੒௕ͱڞʹεέʔϧ͍͚ͯ͠Δ࢓૊ΈͰ͋Δ͜ͱ
    DroidKaigi 2020
    69

    View Slide

  70. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ࣗಈԽ: Automation
    ▸ CI (Continuous Integration) ΍ CD (Continuous Delivery)
    ▸ Ϗϧυ
    ▸ ςετ
    ▸ ੩తղੳ
    ▸ ΞϓϦͷ഑৴
    ▸ ೔ʑͷϫʔΫϑϩʔ
    DroidKaigi 2020
    70

    View Slide

  71. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ϏϧυͷࣗಈԽ: Build Automation
    ▸ ࠩ෼͕ਖ਼͘͠ίϯύΠϧͰ͖Δ͜ͱΛอূ
    ▸ ΞϓϦέʔγϣϯͷ഑৴Λ͢ΔͨΊʹඞཁෆՄܽ
    ▸ ػೳ։ൃϒϥϯν: debug ϏϧυͷΈੜ੒
    ▸ master ΍ develop ͳͲ: release Ϗϧυ΋ੜ੒
    DroidKaigi 2020
    71

    View Slide

  72. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ςετͷࣗಈԽ: Test Automation
    ▸ ࠩ෼͕ςετͰ୲อͰ͖Δൣғʹ͓͍ͯਖ਼͘͠ಈ࡞͢Δ͜ͱΛอূ
    ▸ σάϨΛݕ஌͢ΔͨΊͷୈҰา
    DroidKaigi 2020
    72

    View Slide

  73. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ςετͷࣗಈԽ: Test Automation
    ▸ ϓϩμΫτͷ੒௕ͱڞʹςετͷ࣮ߦ࣌ؒ΋૿͍͑ͯ͘
    ▸ ࣮ߦ࣌ؒΛ୹͘͢Δʹ͸…
    ▸ Gradle ͕࢖͑ΔϓϩηεͱϞδϡʔϧΛ૿΍ͯ͠ฒྻੑΛ্͛Δ
    ▸ ςετ࣮ߦ༻ͷίϯςφΛෳ਺্ཱͪ͛ɺ࣮ߦ͢ΔςετΛ෼ࢄ͢Δ
    ▸ ࣌ؒʹґଘ͍ͯͯ͠଴ͭඞཁͷ͋ΔςετΛݮΒ͢
    ▸ etc……
    DroidKaigi 2020
    73

    View Slide

  74. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ςετͷฒྻԽ: Parallelize unit test execution
    DroidKaigi 2020
    74
    ▸ CircleCI Ͱͷྫ
    ▸ ͻͱͭͷεςοϓ಺Ͱෳ਺ͷίϯςφΛىಈ͢Δ࢓૊ΈΛ࢖͏
    ▸ ෳ਺ͷίϯςφͰςετΛಉ࣌ʹ࣮ߦ͢Δ
    ▸ ͦΕͧΕͷίϯςφͰผͷςετΛ࣮ߦ͠ɺશମͰ͔͔Δ࣌ؒΛ୹ॖ͢Δ

    View Slide

  75. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ςετͷฒྻԽ: Parallelize unit test execution
    DroidKaigi 2020
    75
    Container 0 Container 1 Container 2 Container 3
    :module_a:test

    :module_b:test

    ……
    :module_c:test

    :module_d:test

    ……
    :module_e:test

    :module_f:test

    ……
    :module_g:test

    :module_h:test

    ……
    Container 0
    :module_a:test

    :module_b:test

    :module_c:test

    :module_d:test

    :module_e:test

    :module_f:test

    ……
    ୯Ұίϯςφͷ৔߹
    ෳ਺ίϯςφͷ৔߹

    View Slide

  76. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ςετͷฒྻԽ: Parallelize unit test execution
    DroidKaigi 2020
    76
    ▸ ୯ҰϞδϡʔϧͷ৔߹
    ▸ CircleCI CLI Λ࢖֤ͬͯίϯςφ͝ͱʹϑΝΠϧΛৼΓ෼͚Δ

    circleci tests glob "**/test""/**"/*.kt" | circleci tests split

    ▸ ϑΝΠϧ໊ΛΫϥε໊ʹม׵ͯ͋͛͠Ε͹ɺGradle ͷύϥϝʔλʹ౉ͤΔ
    ▸ ෳ਺Ϟδϡʔϧͷ৔߹
    ▸ ֤ίϯςφ͝ͱʹϞδϡʔϧΛৼΓ෼͚ΔॲཧΛࣗ෼Ͱॻ͘

    View Slide

  77. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ςετͷฒྻԽ: Parallelize unit test execution
    "// The index number of container this execution is on.

    val containerIndex = System.getenv("CIRCLE_NODE_INDEX")"?.toInt() "?: 0

    "// Total number of containers running in parallel.

    val totalContainer = System.getenv("CIRCLE_NODE_TOTAL")"?.toInt() "?: 0

    "// Select modules to run unit tests on the container

    val modules = project.subprojects

    .withIndex()

    .filter { it.index % totalContainer "== containerIndex }

    .map { it.value }

    "// Execute tests

    modules.forEach { module "->

    "./gradlew :${module.name}:test".runCommand()

    }
    DroidKaigi 2020

    View Slide

  78. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ੩తղੳͷࣗಈԽ: Static Analysis Automation
    ▸ ੩తղੳΛࣗಈԽ͠ίʔυϨϏϡʔʹ໾ཱͯΔ
    ▸ ಈ͔͢λΠϛϯά͸ Pull Request ͕Ͱ͖ͨ࣌ͳͲ
    ▸ ੩తղੳͷ݁ՌΛϨϙʔτ͢Δ
    ▸ xml ΍ html ͷϨϙʔτϑΝΠϧ΋ CI ͷ੒Ռ෺ͱଊ͑Δ
    ▸ Pull Request ͕͋Ε͹ɺϨϙʔτͷ಺༰Λίϝϯτ͢Δ
    DroidKaigi 2020
    78

    View Slide

  79. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ഑৴ͷࣗಈԽ: Delivery Automation
    ▸ มߋΛ͍ͭͰ΋ࢼͤΔΑ͏ʹͯ͠ɺϓϩμΫτͷϑΟʔυόοΫϧʔϓΛ࡞Δ
    ▸ ΞϓϦέʔγϣϯͷϏϧυ͕ऴΘͬͨΒ഑৴
    ▸ Firebase App Distribution, DeployGate, HockeyApp, etc…
    DroidKaigi 2020
    79

    View Slide

  80. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ϫʔΫϑϩʔͷࣗಈԽ: Automation
    ▸ ख࡞ۀͷखؒΛͳ͘͠ɺώϡʔϚϯΤϥʔͷ༨஍Λখ͘͢͞Δ
    DroidKaigi 2020
    80

    View Slide

  81. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ϫʔΫϑϩʔͷࣗಈԽ: Automation
    ▸ e.g. QA ϑΣʔζͰͷόάमਖ਼Λ master ʹऔΓࠐΉ࡞ۀͷࣗಈԽ
    ▸ Ϛʔδͷࠩ෼Λখͯ͘͞͠ɺίϯϑϦΫτͷϦεΫΛԼ͛Δ
    ▸ e.g. release ϒϥϯνͷόάमਖ਼Λຖ೔ master ΁औΓࠐΉ
    ▸ e.g. master ϒϥϯνͷߋ৽Λຖ೔֤։ൃϒϥϯν΁औΓࠐΉ
    ▸ ϒϥϯνઓུʹ߹Θ࣮ͤͯ૷ɾӡ༻͢Δ
    DroidKaigi 2020
    81

    View Slide

  82. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ϫʔΫϑϩʔͷࣗಈԽ: Automation
    ▸ QA ϑΣʔζͰͷόάमਖ਼Λ master ʹऔΓࠐΉ࡞ۀͷࣗಈԽ
    DroidKaigi 2020
    82
    master
    release"/*

    master
    release"/*





    releaseϒϥϯνӡ༻ͷ

    ࠷ޙʹϚʔδ͢Δ৔߹
    releaseϒϥϯνΛఆظతʹ

    ࣗಈͰϚʔδ͢Δ৔߹

    View Slide

  83. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ϫʔΫϑϩʔͷࣗಈԽ: How to automate a workflow
    ▸ CLI ͰࣗಈԽͨ͠λεΫΛ࣮ߦͰ͖ΔΑ͏ʹ͢Δ
    ▸ Shell Script
    ▸ Gradle Custom Tasks
    ▸ etc…
    DroidKaigi 2020
    83

    View Slide

  84. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    Gradle Custom Tasks ʹΑΔࣗಈԽ: Automation with Gradle Custom Tasks
    ▸ Gradle Custom Tasks ͷ࡞Γํ
    ▸ project_root/buildSrc ϞδϡʔϧʹλεΫͷ࣮૷Λॻ͘
    ▸ project_root/build.gradle.kts ͰλεΫͷొ࿥Λ͢Δ
    DroidKaigi 2020
    84

    View Slide

  85. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    Gradle Custom Tasks ʹΑΔࣗಈԽ: Automation with Gradle Custom Tasks
    "// buildSrc/build.gradle.kts

    repositories {

    jcenter()

    }

    plugins {

    `kotlin-dsl`

    `java-gradle-plugin`

    }

    dependencies {

    implementation(gradleApi())

    }
    DroidKaigi 2020

    View Slide

  86. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    Gradle Custom Tasks ʹΑΔࣗಈԽ: Automation with Gradle Custom Tasks
    "// buildSrc/src/main/kotlin/com/example/MyCustomTask.kt

    open class MyCustomTask : DefaultTask() {

    @Input lateinit var parameter: String

    @TaskAction

    fun doAction() {

    "// … do your job!

    }

    }
    DroidKaigi 2020

    View Slide

  87. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    Gradle Custom Tasks ʹΑΔࣗಈԽ: Automation with Gradle Custom Tasks
    "// buildSrc/src/main/kotlin/com/example/MyCustomTask.kt

    open class MyCustomTask : DefaultTask() {

    @Input lateinit var parameter: String

    @TaskAction

    fun doAction() {

    "// … do your job!

    }

    }
    DroidKaigi 2020

    View Slide

  88. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    Gradle Custom Tasks ʹΑΔࣗಈԽ: Automation with Gradle Custom Tasks
    "// buildSrc/src/main/kotlin/com/example/MyCustomTask.kt

    open class MyCustomTask : DefaultTask() {

    @Input lateinit var parameter: String

    @TaskAction

    fun doAction() {

    "// … do your job!

    }

    }
    DroidKaigi 2020

    View Slide

  89. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    Gradle Custom Tasks ʹΑΔࣗಈԽ: Automation with Gradle Custom Tasks
    "// buildSrc/src/main/kotlin/com/example/MyCustomTask.kt

    open class MyCustomTask : DefaultTask() {

    @Input lateinit var parameter: String

    @TaskAction

    fun doAction() {

    "// … do your job!

    }

    }
    DroidKaigi 2020

    View Slide

  90. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    Gradle Custom Tasks ʹΑΔࣗಈԽ: Automation with Gradle Custom Tasks
    "// build.gradle.kts in the project root

    tasks.register("myTask") {

    parameter = "Hello, World"

    }
    DroidKaigi 2020

    View Slide

  91. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ίʔυϨϏϡʔ: Code Review
    ▸ ઃܭํ਑΍໋໊ͷΘ͔Γ΍͢͞ɺߟྀ࿙ΕνΣοΫʹਓͷྗΛूத͍ͨ͠
    ▸ ࣗಈԽͰ͖Δͱ͜Ζ͸ͲΜͲΜࣗಈԽ͢Δ
    ▸ ίʔσΟϯάن໿ͷڞ༗: .editorconfig Λ࡞͓ͬͯ͘
    ▸ ίʔσΟϯάن໿ʹै͍ͬͯΔ͔νΣοΫ: CI Ͱ ktlint ͳͲΛಈ͔͢
    ▸ Α͋͘Δؒҧ͍ΛνΣοΫ: CI Ͱ android lint ͳͲΛಈ͔͢
    DroidKaigi 2020
    91

    View Slide

  92. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ίʔυϨϏϡʔ: Code Review
    ▸ υϝΠϯ஌ࣝΛ࣋ͬͨਓʹϨϏϡʔΛґཔ͠ɺίʔυϨϏϡʔͷ࣭Λ্͛Δ
    ▸ ͩΕʹϨϏϡʔͯ͠΄͍͔͠ઃఆ͢Δ
    ▸ CODEOWNERS
    ▸ σΟϨΫτϦ͝ͱʹઃఆ
    ▸ σΟϨΫτϦ഑ԼͷϑΝΠϧʹࠩ෼Λ࡞ΔͱɺࣗಈͰઃఆͨ͠ਓ͕ϨϏϡϫʔ
    ʹͳΔ
    DroidKaigi 2020
    92

    View Slide

  93. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ίʔυϨϏϡʔ: Code Review
    # KeithYokoma is the owner of Module A

    /module_a/ @KeithYokoma

    # Anyone in team_a can review changes in Module B

    /module_b/ @team_a

    # Either KeithYokoma or someone in team_a can review

    /module_c/ @KeithYokoma @team_a
    DroidKaigi 2020

    View Slide

  94. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ίʔυϨϏϡʔ: Code Review
    ▸ ίʔυϨϏϡʔʹૉૣ͘औΓ૊ΜͰ։ൃαΠΫϧΛͳΊΒ͔ʹճ͢
    ▸ ίʔυϨϏϡʔͷϦΫΤετΛ௨஌͢Δ
    ▸ Pull Reminders
    ▸ ௚઀ࣗ෼Ѽͯʹ௨஌Λ͢Δ
    ▸ ಛఆͷνϟϯωϧͰɺApproval ͷͳ͍ Pull Request Λ௨஌͢Δ
    DroidKaigi 2020
    94

    View Slide

  95. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ίʔυϨϏϡʔ: Code Review
    ▸ ίʔυϨϏϡʔʹૉૣ͘औΓ૊ΜͰ։ൃαΠΫϧΛͳΊΒ͔ʹճ͢
    ▸ Pull Request ͷઆ໌Λ͏·͘ॻ͘
    ▸ Ͳ͏͍͏໨తͷࠩ෼ͳͷ͔Λઆ໌ͯ͠΄͍͠
    ▸ ࠩ෼ΛಡΉ্Ͱͷલఏ஌͕ࣝ͋Ε͹આ໌ͯ͠΄͍͠
    DroidKaigi 2020
    95

    View Slide

  96. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ίʔυϨϏϡʔ: Code Review
    ▸ νʔϜͰڞ௨ͷϑΥʔϚοτ͕͋Δͱॻ͖΍͘͢ͳΔ
    ▸ ςϯϓϨʔτͷ׆༻
    ▸ GitHub: .github/PULL_REQUEST_TEMPLATE.md
    ▸ GitLab: .gitlab/merge_request_templates/XXX.md
    DroidKaigi 2020
    96

    View Slide

  97. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ςϯϓϨʔτྫ: Example of pull request template
    "## Summary



    "## Kind



    "## Details



    "## Links



    - Task Ticket:

    - Design Doc:

    - Crashlytics Issue:

    "## Screenshot



    Before | After

    :——:|:——:

    |
    DroidKaigi 2020

    View Slide

  98. Release
    ϦϦʔε
    DroidKaigi 2020

    View Slide

  99. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ϦϦʔε: Release
    ▸ ҆શͳϦϦʔεΛࢧ͑Δ࢓૊Έͮ͘Γ
    ▸ ϑΟʔνϟʔϑϥά
    ▸ ϦϦʔετϨΠϯ
    ▸ ϞχλϦϯά
    DroidKaigi 2020
    99

    View Slide

  100. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ϑΟʔνϟʔϑϥά: Feature Flag
    ▸ ػೳͷग़͠෼͚Λίϯτϩʔϧͯ͠ૉૣ͍։ൃαΠΫϧΛอͭ
    ▸ ϑΟʔνϟʔϑϥάΛ࢖͏৔໘
    ▸ ஈ֊తʹػೳΛެ։͍ͨ͠ͱ͖
    ▸ 1౓ͷϦϦʔεαΠΫϧΛ௒͑ͯػೳ։ൃΛ͍ͨ͠ͱ͖
    DroidKaigi 2020
    100

    View Slide

  101. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ϑΟʔνϟʔϑϥάΛ࢖͏ྫ: Example usage of Feature Flag
    ▸ ஈ֊తʹػೳΛެ։͍ͨ͠ͱ͖
    ▸ Firebase Remote Config ΍ɺࣗલͷ࢓૊ΈͳͲͰઃఆΛม͑Δ
    ▸ ༷ʑͳଐੑ஋͔ΒઃఆΛੜ੒Ͱ͖Δ
    ▸ ৽͍͠ػೳͷ։ൃ͕ऴΘΓɺঃʑʹ։์͢Δͱ͖ʹ༗ޮ
    DroidKaigi 2020
    101

    View Slide

  102. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ϑΟʔνϟʔϑϥάΛ࢖͏ྫ: Example usage of Feature Flag
    ▸ 1౓ͷϦϦʔεαΠΫϧΛ௒͑ͯػೳ։ൃΛ͍ͨ͠ͱ͖
    ▸ Git ͷϒϥϯνΛࡉ͔͘࡞ͬͯϚʔδࠩ͠෼ΛੵΈ্͍͛ͯ͘
    ▸ ࣮૷ͷ޻෉Ͱ։ൃ్தͷػೳΛݟ͑ͳ͍ɾ࢖͑ͳ͍Α͏ʹ࠹͙
    DroidKaigi 2020
    102
    master
    Merge

    Feature A-1 v1.1.0
    Merge

    Feature A-2 v1.2.0
    Merge

    Feature A-3 v1.2.1
    Finalize

    Feature A v1.3.0

    View Slide

  103. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ϑΟʔνϟʔϑϥάΛ࢖͏ྫ: Example usage of Feature Flag
    ▸ ࣮૷ͷ޻෉ͷ࢓ํ
    ▸ BuildConfig ʹϑΟʔνϟʔϑϥάΛఆٛ͢Δ
    ▸ UI ͷͭͳ͗͜ΈΛ࠷ޙʹ࣮ࢪ
    ▸ ։ൃऀ޲͚ͷઃఆը໘Ͱ༗ޮɾແޮΛ੾Γସ͑Δ
    ▸ etc…
    DroidKaigi 2020
    103

    View Slide

  104. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ϑΟʔνϟʔϑϥάΛ࢖͏ྫ: Example usage of Feature Flag
    ▸ BuildConfig ʹϑΟʔνϟʔϑϥάΛఆٛ͢Δ৔߹
    ▸ e.g. طଘը໘ʹ৽͍͠ػೳΛ΋ͬͨίϯϙʔωϯτΛ௥Ճ͢Δ࣮૷
    DroidKaigi 2020
    104
    master
    Merge

    Feature A-1 v1.1.0
    Merge

    Feature A-2 v1.2.0
    Merge

    Feature A-3 v1.2.1
    Finalize

    Feature A v1.3.0
    ৽͍͠ػೳ޲͚ͷ

    UI ίϯϙʔωϯτ࡞੒
    ৽͍͠ػೳͷ

    Ϟσϧ࣮૷
    Ϟσϧͱ UI ίϯϙʔωϯτ
    Λͭͳ͗͜Ή
    BuildConfig ͷϑϥάΛ

    ༗ޮԽ͢Δ

    View Slide

  105. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ϑΟʔνϟʔϑϥάΛ࢖͏ྫ: Example usage of Feature Flag
    ▸ UI ͷͭͳ͗ࠐΈΛ࠷ޙʹ΍Δ৔߹
    ▸ e.g. ৽͍͠ػೳͷઃఆը໘Λ࡞ΓɺઃఆҰཡʹ߲໨Λ଍࣮͢૷
    DroidKaigi 2020
    105
    master
    Merge

    Feature A-1 v1.1.0
    Merge

    Feature A-2 v1.2.0
    Merge

    Feature A-3 v1.2.1
    Finalize

    Feature A v1.3.0
    ͋ͨΒ͍͠ઃఆը໘ͷ

    ϨΠΞ΢τ࡞੒
    ͋ͨΒ͍͠ઃఆը໘ͷ

    Ϟσϧ࣮૷
    Ϟσϧͱը໘Λͭͳ͗͜Ή
    ઃఆҰཡʹɺ৽͍͠ઃఆը໘
    ΁ͷಋઢΛ଍͢

    View Slide

  106. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ϑΟʔνϟʔϑϥάͷ஫ҙ఺: Caveats of Feature Flag
    ▸ ϑΟʔνϟʔϑϥάͰ෼཭ͨ͠ػೳͷ։ൃ͕ଞͷطଘػೳʹӨڹΛ༩͑ͳ͍
    DroidKaigi 2020
    106
    Existing Feature New Feature
    Existing Logic New Logic


    Shared Logic
    Existing Feature New Feature

    View Slide

  107. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ϑΟʔνϟʔϑϥάͷ஫ҙ఺: Caveats of Feature Flag
    ▸ "Release toggles are the last thing you should do" by Martin Fowler
    ▸ ϑϥάΛ΋ͭ͜ͱͷίετ͸গͳ͔Βͣ͋Δ
    ▸ ༗ޮԽ͢ΔλΠϛϯά
    ▸ ࢖Θͳ͘ͳͬͨΒ࡟আ͢Δ
    ▸ ϑϥάΛ࣋ͭ΄͔ʹऔΓ͏Δબ୒ࢶ
    ▸ UI ͷͭͳ͗͜ΈΛ࠷ޙʹ͢Δ
    ▸ ػೳͦͷ΋ͷΛࡉ͔͘Θ͚ɺখ͘͞ϦϦʔε͢Δ
    DroidKaigi 2020
    107

    View Slide

  108. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ϦϦʔετϨΠϯ: Release Train
    ▸ ϦϦʔεεέδϡʔϧΛݻఆͯ͠ܧଓతʹ҆શͳϦϦʔεΛ͢Δ
    ▸ ϦϦʔετϨΠϯӡ༻ͷେݪଇ
    ▸ ܾ·ͬͨεέδϡʔϧ͔Βٯࢉͯ͠։ൃΛ͍ͯ͘͠
    ▸ ؒʹ߹Θͳ͍΋ͷ͸࣍ͷϦϦʔε೔Λ଴ͭ
    ▸ ن໛ͷେ͖ͳ։ൃͳΒͰ͸ͷ՝୊ղܾํ๏
    ▸ ͨ͘͞ΜͷϓϩδΣΫτؒͷௐ੔͝ͱΛ෼͔Γ΍͍ͨ͘͢͠
    DroidKaigi 2020
    108

    View Slide

  109. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ؂ࢹମ੍: Metrics monitoring
    ▸ ૉૣ͘ҟৗʹؾ͖ͮɺૉૣ͘ରࡦΛଧͭ
    ▸ ҟৗͷछྨ
    ▸ ΞϓϦͷΫϥογϡ
    ▸ ྫ֎έʔεͷසൃ
    ▸ ϦάϨογϣϯͷൃੜ
    ▸ ύϑΥʔϚϯεͷྼԽ
    DroidKaigi 2020
    109

    View Slide

  110. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ؂ࢹମ੍: Metrics monitoring
    ▸ ࢓૊ΈͰղܾ: Crashlytics
    ▸ Ϋϥογϡ݅਺͕୹࣌ؒͰஶ͘͠৳ͼͨͱ͖ʹ௨஌͢Δ
    DroidKaigi 2020
    110

    View Slide

  111. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ؂ࢹମ੍: Metrics monitoring
    ▸ ࢓૊ΈͰղܾ: Crashlytics
    ▸ ΞϥʔτΛ Slack ʹ౤ߘ͢Δ
    ▸ ৽نͷΫϥογϡΛݕ஌ͨ͠Β

    Issue Tracker ʹνέοτΛ࡞Δ
    ▸ etc…
    DroidKaigi 2020
    111

    View Slide

  112. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ؂ࢹମ੍: Metrics monitoring
    ▸ ࢓૊ΈͰղܾ: Crashlytics
    ▸ ΞϥʔτΛ Slack ʹ౤ߘ͢Δ
    ▸ ৽نͷΫϥογϡΛݕ஌ͨ͠Β

    Issue Tracker ʹνέοτΛ࡞Δ
    ▸ etc…
    DroidKaigi 2020
    112

    View Slide

  113. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ؂ࢹମ੍: Metrics monitoring
    ▸ ໰୊ͷཧղΛॿ͚Δϩάͷऔಘ
    ▸ ͲΜͳঢ়گԼͰ͓͖ͨ໰୊͔
    ▸ Ͳ͏͍͏ܦ࿏Ͱ͓͖ͨ໰୊͔
    ▸ σόΠε͝ͱͷ܏޲͸͋Δ͔
    ▸ ΞϓϦόʔδϣϯ͝ͱͷ܏޲͸͋Δ͔
    ▸ etc…
    DroidKaigi 2020
    113

    View Slide

  114. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ϩΪϯάͷઃܭ: Log collection architecture
    ▸ e.g. Timber + Crashlytics
    ▸ ϩάΛు͖ͩ͢ϝιουΛ Timber ʹू໿
    ▸ Timber.d("debug log")

    ▸ Timber.i("info log")

    ▸ Timber.e(exception, "error log")

    ▸ ϩάΛు͘ϝιουݺͼग़͠ΛϑοΫͯ͠ Crashlytics Ͱूܭ
    DroidKaigi 2020
    114

    View Slide

  115. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ϩΪϯάͷઃܭ: Log collection architecture
    class CrashlyticsTree : Timber.Tree() {

    override fun log(priority: Int, tag: String?, message: String, throwable: Throwable?) {

    when (priority) {

    Log.INFO "-> Crashlytics.log(priority, tag, message)

    Log.WARN "-> Crashlytics.log(priority, tag, message)

    Log.ERROR "-> {

    Crashlytics.log(priority, tag, message)

    throwable"?.let {

    Crashlytics.logException(it)

    }

    }

    }

    }

    }

    DroidKaigi 2020

    View Slide

  116. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ϩΪϯάͷઃܭ: Log collection architecture
    class CrashlyticsTree : Timber.Tree() {

    override fun log(priority: Int, tag: String?, message: String, throwable: Throwable?) {

    when (priority) {

    Log.INFO "-> Crashlytics.log(priority, tag, message)

    Log.WARN "-> Crashlytics.log(priority, tag, message)

    Log.ERROR "-> {

    Crashlytics.log(priority, tag, message)

    throwable"?.let {

    Crashlytics.logException(it)

    }

    }

    }

    }

    }

    DroidKaigi 2020

    View Slide

  117. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ΧελϜΩʔͷ௥Ճ: Adding custom keys
    ▸ Crashlytics ͷϨϙʔτʹ௥Ճ৘ใΛ෇༩͢Δ͘͠Έ
    ▸ ΞϓϦͷ࣋ͭઃఆ஋
    ▸ e.g. XXX ͷػೳ͕༗ޮ͔Ͳ͏͔
    ▸ Ϣʔβͷঢ়ଶ
    ▸ e.g. YYY ͷνϡʔτϦΞϧ͸ऴΘ͔ͬͨͲ͏͔
    DroidKaigi 2020
    117

    View Slide

  118. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ΧελϜΩʔͷ௥Ճ: Adding custom keys
    class SettingsFragment : PreferenceFragmentCompat() {

    override fun onCreatePreferences(

    savedState: Bundle?,

    rootKey: String?

    ) {

    setPreferencesFromResource(R.xml.pref, rootKey)

    val notificationPreference = findPreference("enable_notification")

    notificationPreference"?.setOnPreferenceChangeListener { _, newValue "->

    "// Put a custom key for preference value

    Crashlytics.setBool("enable_notification", newValue)

    true

    }

    }

    }
    DroidKaigi 2020

    View Slide

  119. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ΧελϜΩʔͷ௥Ճ: Adding custom keys
    class SettingsFragment : PreferenceFragmentCompat() {

    override fun onCreatePreferences(

    savedState: Bundle?,

    rootKey: String?

    ) {

    setPreferencesFromResource(R.xml.pref, rootKey)

    val notificationPreference = findPreference("enable_notification")

    notificationPreference"?.setOnPreferenceChangeListener { _, newValue "->

    "// Put a custom key for preference value

    Crashlytics.setBool("enable_notification", newValue)

    true

    }

    }

    }
    DroidKaigi 2020

    View Slide

  120. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ΧελϜΩʔͷ௥Ճ: Adding custom keys
    DroidKaigi 2020
    120

    View Slide

  121. Wrap up
    ·ͱΊ
    DroidKaigi 2020

    View Slide

  122. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ·ͱΊ: Wrap up
    ▸ DX Λܧଓతʹվળ͍ͯ͘͜͠ͱͰ…
    ▸ ૉૣ͍ػೳ࣮૷͕͠΍͘͢ͳΔ
    ▸ ૉૣ͍ৼΓฦΓ͕͠΍͘͢ͳΔ
    ▸ ͦͷ݁ՌɺνʔϜ΋ϓϩμΫτ΋͙Μ͙Μ੒௕͍ͯ͘͠
    ▸ ܧଓ͸ྗͳΓ
    DroidKaigi 2020
    122

    View Slide

  123. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ·ͱΊ: Wrap up
    ▸ ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑ΔྖҬ
    ▸ ࣄલ४උ
    ▸ ઃܭ
    ▸ ։ൃϓϩηε
    ▸ ϦϦʔε
    DroidKaigi 2020
    123

    View Slide

  124. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷDXΛࢧ͑Δٕज़
    ͍͞͝ʹ: Wrap up
    ▸ ΋͏૸Γ࢝Ί͍ͯΔϓϩδΣΫτͰ DX Λվળ͢Δʹ͸…
    ▸ ݱঢ়෼ੳΛͯ͠νʔϜͷ໨ઢΛ߹ΘͤΔ
    ▸ KPT ͰఆੑతʹνʔϜͷߟ͑Λ·ͱΊͯΈΔ
    ▸ ςετΧόϨοδ΍ܯࠂ਺ͷਪҠͳͲΛ࢖ͬͯఆྔతʹ࣭ΛଌͬͯΈΔ
    ▸ ՝୊ײΛἧ͑ΔͱɺԿ͔ΒऔΓ૊ΉͱΑ͍͔΋෼͔Γ΍͘͢ͳΔ
    DroidKaigi 2020
    124

    View Slide

  125. ࣋ଓతͳΞϓϦ։ൃͷͨΊͷ
    DXΛࢧ͑Δٕज़
    KeithYokoma (Keishin Yokomaku) /

    DroidKaigi 2020

    View Slide