Slide 1

Slide 1 text

ZOZOTOWN AndroidへのJetpack Composeの導入
 2021/08/05(Thu)
 Android Meetup【ZOZOテクノロジーズ × サイバーエージェント × GMOペパボ】
 株式会社ZOZOテクノロジーズ
 ZOZOTOWN本部 ZOZOアプリ部 Androidチーム
 高橋 啓太
 
 Copyright © ZOZO Technologies, Inc.

Slide 2

Slide 2 text

© ZOZO Technologies, Inc. 株式会社ZOZOテクノロジーズ
 ZOZOTOWN本部 ZOZOアプリ部 Androidチーム
 高橋 啓太
 2020年新卒入社
 日本酒が好きです
 
 2

Slide 3

Slide 3 text

© ZOZO Technologies, Inc. https://zozo.jp/
 3 ● 日本最大級のファッション通販サイト
 ● 1,400以上のショップ、8,200以上のブランドの取り扱い(ともに2021年3月 末時点)
 ● 常時83万点以上の商品アイテム数と毎日平均2,900点以上の新着 商品 を掲載
 ● コスメ専門モール「ZOZOCOSME」や靴の専門モール
 「ZOZOSHOES」、ラグジュアリー&デザイナーズゾーン
 「ZOZOVILLA」を展開
 ● 即日配送サービス
 ● ギフトラッピングサービス
 ● ツケ払い など


Slide 4

Slide 4 text

© ZOZO Technologies, Inc. 4 Jetpack Compose?

Slide 5

Slide 5 text

© ZOZO Technologies, Inc. 5 Jetpack Composeとは ● 宣言的UIのツールキット ○ Thinking in Compose | Jetpack Compose ● Kotlinのコード上でUIの実装が可能になる ● XMLを用いた既存のレイアウトと相互運用可能 ● 1.0.0(stable)がリリースされました🎉

Slide 6

Slide 6 text

© ZOZO Technologies, Inc. 6 Jetpack Composeとは Composableアノテーションを付与した関数でUIを定義する @Composable fun Greeting(name: String) { Text(text = "Hello $name!") } Android デベロッパー

Slide 7

Slide 7 text

© ZOZO Technologies, Inc. 7 ZOZOTOWN Androidへの導入

Slide 8

Slide 8 text

© ZOZO Technologies, Inc. 8 導入のステップ 1. 開発環境の整備 2. 課題の洗い出し 3. 課題の解決

Slide 9

Slide 9 text

© ZOZO Technologies, Inc. 9 導入のステップ 1. 開発環境の整備 ○ Android Studio Arctic Foxでの既存プロジェクトのビルド ○ ライブラリ等のアップデート 2. 課題の洗い出し 3. 課題の解決

Slide 10

Slide 10 text

© ZOZO Technologies, Inc. 10 Android Studio Arctic Foxでの既存プロジェクトのビルド ● Android Studio Arctic FoxではJetpack Composeの開発がサポートされた ○ Compose UIのプレビューなど ● AGP 7.0.0 & JDK 11を導入 ○ Arctic Fox Canary 9からAGP 7.0.0を使用する場合にJDK 11が求められるように なった ○ AGP 7.0: Next major release for the Android Gradle plugin ● Hiltのアップデート ● ZOZOTOWN Androidをビルドし、問題なくビルドできることを確認

Slide 11

Slide 11 text

© ZOZO Technologies, Inc. 11 ライブラリ等のアップデート ● Jetpack Compose beta08からはKotlin 1.5以上が必要となるためアップデートを実施 ○ 各種ドキュメントの確認と影響箇所の調査 ■ ドキュメントの内容をチームで読み合わせ ■ Compatibility guide for Kotlin 1.5 ■ Kotlin releases ● androidx.lifecycleのアップデート ○ Composable内でflowWithLifecycleを利用するため ○ Lifecycle

Slide 12

Slide 12 text

© ZOZO Technologies, Inc. 12 導入のステップ 1. 開発環境の整備 2. 課題の洗い出し ○ プロトタイプ実装 3. 課題の解決

Slide 13

Slide 13 text

© ZOZO Technologies, Inc. 13 プロトタイプ実装 ● 設計前にプロトタイプ実装とレビューを繰り返し行った ○ Compose導入時の課題を洗い出すため 実装 レビュー

Slide 14

Slide 14 text

© ZOZO Technologies, Inc. 14 課題の洗い出し プロトタイプ実装とレビューによって明らかになった課題 ● 既存のZOZOTOWN AndroidのUIの状態管理方法がComposeに適していない ○ UIの状態管理・Eventの処理方法を見直す必要があった ● 無秩序なComposable作成によるComposableの再利用性・可読性低下 ○ チームでの開発に向けてComposableの設計ルールを制定する必要があった

Slide 15

Slide 15 text

© ZOZO Technologies, Inc. 15 導入のステップ 1. 開発環境の整備 2. 課題の洗い出し 3. 課題の解決 ○ UIの状態管理・Eventの処理方法の見直し ○ Composable設計ルールの制定

Slide 16

Slide 16 text

© ZOZO Technologies, Inc. 16 UIの状態管理・Event処理方法の見直し 既存のZOZOTOWN AndroidのUIの状態管理方法がComposeに適していない ● Composeが採用されているプロジェクトの実装を調査 ● Composeに適した設計を検討した ○ UIの状態管理方法 ○ Eventの処理方法

Slide 17

Slide 17 text

© ZOZO Technologies, Inc. 17 ComposeのUI更新 “Compose は常に、UI のうち再描画する必要がある部分をインテリジェントに選択します。” Compose の思想 | Jetpack Compose 更新差分のあるViewDataのみを保持している

Slide 18

Slide 18 text

© ZOZO Technologies, Inc. 18 ComposeのUI更新 “Compose は常に、UI のうち再描画する必要がある部分をインテリジェントに選択します。” Compose の思想 | Jetpack Compose → 差分を意識してデータを通知する必要がない 更新差分のあるViewDataのみを保持している

Slide 19

Slide 19 text

© ZOZO Technologies, Inc. 19 UI更新の流れ(既存のZOZOTOWN Android) Fragment ViewModel View View

Slide 20

Slide 20 text

© ZOZO Technologies, Inc. 20 UI更新の流れ(既存のZOZOTOWN Android) Fragment ViewModel View View アプリケーションの 状態更新

Slide 21

Slide 21 text

© ZOZO Technologies, Inc. 21 Fragment ViewModel 更新差分を通知 View View アプリケーションの 状態更新 UI更新の流れ(既存のZOZOTOWN Android)

Slide 22

Slide 22 text

© ZOZO Technologies, Inc. 22 Fragment ViewModel 更新差分を通知 View View 更新 アプリケーションの 状態更新 UI更新の流れ(既存のZOZOTOWN Android)

Slide 23

Slide 23 text

© ZOZO Technologies, Inc. 23 Fragment ViewModel 更新差分を通知 View View 更新 Composeには適さないデー タ構造 アプリケーションの 状態更新 UI更新の流れ(既存のZOZOTOWN Android)

Slide 24

Slide 24 text

© ZOZO Technologies, Inc. 24 UIの状態管理(既存のZOZOTOWN Android) ViewData data class ItemViewData( val name: String, val price: String, ) ● UIに表示するデータをまとめたdata class ● カスタムビュー単位で作成

Slide 25

Slide 25 text

© ZOZO Technologies, Inc. 25 ViewState sealed class ViewState { object Initialized(val shopviewData: HogeViewData) : ViewState() data class Favorited(val itemViewData: ItemViewData) : ViewState() } ● UIの状態を表すsealed class ● 画面単位で作成 ● ViewDataを保持する ● ViewStateの切り方についての明確なルールは存在しない UIの状態管理(既存のZOZOTOWN Android)

Slide 26

Slide 26 text

© ZOZO Technologies, Inc. 26 差分のあるデータのみを通知 Fragment/ActivityへViewStateを通知するLiveData/Flow class HogeViewModel : ViewModel() { private val _viewState = MutableStateFlow() val viewState: StateFlow = _viewState ... private fun addFavorite(item: Item) { ... _viewState.value = ViewState.Favorited(itemViewData) } sealed class ViewState { object Initial : ViewState() data class Initialized( val shopViewData: ShopViewData, val itemViewData: ItemViewData, ) : ViewState() data class Favorited( val itemViewData: ItemViewData, ) : ViewState() } } UIの状態管理(既存のZOZOTOWN Android)

Slide 27

Slide 27 text

© ZOZO Technologies, Inc. class HogeViewModel : ViewModel() { private val _viewState = MutableStateFlow() val viewState: StateFlow = _viewState private fun addFavorite(item: Item) { ... _viewState.value = ViewState.Favorited(itemViewData) } sealed class ViewState { object Initial : ViewState() data class Initialized( val shopViewData: ShopViewData, val itemViewData: ItemViewData, ) : ViewState() data class Favorited( val itemViewData: ItemViewData, ) : ViewState() } } 27 Activity/FragmentにViewStateを通知するFlow/LiveData UIの状態管理(既存のZOZOTOWN Android)

Slide 28

Slide 28 text

© ZOZO Technologies, Inc. class HogeViewModel : ViewModel() { private val _viewState = MutableStateFlow() val viewState: StateFlow = _viewState private fun addFavorite(item: Item) { ... _viewState.value = ViewState.Favorited(itemViewData) } sealed class ViewState { object Initial : ViewState() data class Initialized( val shopViewData: ShopViewData, val itemViewData: ItemViewData, ) : ViewState() data class Favorited( val itemViewData: ItemViewData, ) : ViewState() } } 28 データの更新とFlow/LiveDataにViewStateを流すメソッド UIの状態管理(既存のZOZOTOWN Android)

Slide 29

Slide 29 text

© ZOZO Technologies, Inc. 29 class HogeViewModel : ViewModel() { private val _viewState = MutableStateFlow() val viewState: StateFlow = _viewState private fun addFavorite(item: Item) { ... _viewState.value = ViewState.Favorited(itemViewData) } sealed class ViewState { object Initial : ViewState() data class Initialized( val shopViewData: ShopViewData, val itemViewData: ItemViewData, ) : ViewState() data class Favorited( val itemViewData: ItemViewData, ) : ViewState() } } UIの状態管理(既存のZOZOTOWN Android)

Slide 30

Slide 30 text

© ZOZO Technologies, Inc. class HogeViewModel : ViewModel() { private val _viewState = MutableStateFlow() val viewState: StateFlow = _viewState private fun addFavorite(item: Item) { ... _viewState.value = ViewState.Favorited(itemViewData) } sealed class ViewState { object Initial : ViewState() data class Initialized( val shopViewData: ShopViewData, val itemViewData: ItemViewData, ) : ViewState() data class Favorited( val itemViewData: ItemViewData, ) : ViewState() } } 30 更新差分のあるViewDataのみを保持している UIの状態管理(既存のZOZOTOWN Android)

Slide 31

Slide 31 text

© ZOZO Technologies, Inc. class HogeViewModel : ViewModel() { private val _viewState = MutableStateFlow() val viewState: StateFlow = _viewState private fun addFavorite(item: Item) { ... _viewState.value = ViewState.Favorited(itemViewData) } sealed class ViewState { object Initial : ViewState() data class Initialized( val shopViewData: ShopViewData, val itemViewData: ItemViewData, ) : ViewState() data class Favorited( val itemViewData: ItemViewData, ) : ViewState() } } 31 更新差分のあるViewDataのみを保持している Composeでは扱い辛い UIの状態管理(既存のZOZOTOWN Android)

Slide 32

Slide 32 text

© ZOZO Technologies, Inc. 32 ComposeのUI更新 “Compose は常に、UI のうち再描画する必要がある部分をインテリジェントに選択します。” Compose の思想 | Jetpack Compose → 差分を意識してデータを通知する必要がない 更新差分のあるViewDataのみを保持している

Slide 33

Slide 33 text

© ZOZO Technologies, Inc. 33 ComposeのUI更新 “Compose は常に、UI のうち再描画する必要がある部分をインテリジェントに選択します。” Compose の思想 | Jetpack Compose → 差分を意識してデータを通知する必要がない  → これを活用するためには画面全体の状態を一つにまとめる必要がある 更新差分のあるViewDataのみを保持している

Slide 34

Slide 34 text

© ZOZO Technologies, Inc. 34 Composeに適した設計の検討 ● UIの状態管理方法 ● Eventの処理方法

Slide 35

Slide 35 text

© ZOZO Technologies, Inc. 35 Composeに適した設計の検討 ● UIの状態管理方法 ● Eventの処理方法

Slide 36

Slide 36 text

© ZOZO Technologies, Inc. 36 参考実装の調査 参考となるプロジェクトの実装調査: tivi ○ Jetpack Composeで実装されたプロジェクト ○ 既存のZOZOTOWNと近いアーキテクチャ ○ chrisbanes/tivi

Slide 37

Slide 37 text

© ZOZO Technologies, Inc. 37 UIの状態管理方法の見直し 更新差分のあるViewDataのみを保持している data class HogeViewState( val itemViewData: ItemViewData, val shopViewData: ShopViewData, ) 1. 画面全体のViewDataを統合 画面すべてのViewDataを持つViewStateを作成

Slide 38

Slide 38 text

© ZOZO Technologies, Inc. 38 UIの状態管理方法の見直し 差分のあるデータのみを通知 2. 通知方法の見直し private val itemState = MutableStateFlow(ItemViewData.Empty) private val shopState = MutableStateFlow(ShopViewData.Empty) val viewState = combine(itemState, shopState) { item, shop -> HogeViewState( item, shop, ) } アプリケーションやViewの状態をそれぞれFlowで管理

Slide 39

Slide 39 text

© ZOZO Technologies, Inc. 39 UIの状態管理方法の見直し 差分のあるデータのみを通知 アプリケーションやViewの状態をそれぞれFlowで管理 → 監視しているFlowの変更時に、画面全体のViewStateを生成して通知する 2. 通知方法の見直し private val itemState = MutableStateFlow(ItemViewData.Empty) private val shopState = MutableStateFlow(ShopViewData.Empty) val viewState = combine(itemState, shopState) { item, shop -> HogeViewState( item, shop, ) }

Slide 40

Slide 40 text

© ZOZO Technologies, Inc. 40 UIの状態管理方法の見直し 差分のあるデータのみを通知 Composableでは常に画面全体の状態(ViewState)を受け取る 3. ComposableでのViewStateの監視 val viewState by remember(viewModel.viewState, lifecycleOwner) { viewModel.viewState.flowWithLifecycle( lifecycleOwner.lifecycle, Lifecycle.State.STARTED, ) }.collectAsState(HogeViewState.Empty)

Slide 41

Slide 41 text

© ZOZO Technologies, Inc. 41 UIの状態管理方法の見直し Composable Composable Composable Composable ViewModel Flow State ViewState 画面全体の状態を通知

Slide 42

Slide 42 text

© ZOZO Technologies, Inc. 42 Composeに適した設計の検討 ● UIの状態管理方法 ● Eventの処理方法

Slide 43

Slide 43 text

© ZOZO Technologies, Inc. 43 Eventの処理方法 ViewEvent sealed class ViewEvent { object ClickItem : ViewEvent() data class ClickShop(val id: Int) : ViewEvent() } ユーザーインタラクションなどによって発生するイベントを定義したsealed class

Slide 44

Slide 44 text

© ZOZO Technologies, Inc. 44 Eventの処理方法 class HogeViewModel : ViewModel() { private val _viewEvent = MutableSharedFlow() fun dispatchViewEvent(event: ViewEvent) { viewModelScope.launch { _viewEvent.emit(event) } } } Flow/LiveData(SingleLiveEvent)でViewEventを通知

Slide 45

Slide 45 text

© ZOZO Technologies, Inc. 45 Eventの処理方法 @Composable fun HogeScreen(viewModel: HogeViewModel) { ... Column { ... HogeScreen( onItemClick = { viewModel.dispatchViewEvent(HogeViewModel.ViewEvent.ClickItem) } ) } } @Composable fun HogeScreen( onItemClick: () -> Unit ) { Column { ... Item(onItemClick) } }

Slide 46

Slide 46 text

© ZOZO Technologies, Inc. 46 Eventの処理方法 @Composable fun HogeScreen(viewModel: HogeViewModel) { ... Column { ... HogeScreen( onItemClick = { viewModel.dispatchViewEvent(HogeViewModel.ViewEvent.ClickItem) } ) } } @Composable fun HogeScreen( onItemClick: () -> Unit ) { Column { ... Item(onItemClick) } } 各画面のトップレベルのComposableからViewEventを発行

Slide 47

Slide 47 text

© ZOZO Technologies, Inc. 47 Eventの処理方法 @Composable fun HogeScreen(viewModel: HogeViewModel) { ... Column { ... HogeScreen( onItemClick = { viewModel.dispatchViewEvent(HogeViewModel.ViewEvent.ClickItem) } ) } } @Composable fun HogeScreen( onItemClick: () -> Unit ) { Column { ... Item(onItemClick) } } 下層のComposableからはViewEventを意識しない

Slide 48

Slide 48 text

© ZOZO Technologies, Inc. 48 Eventの処理方法 @Composable fun HogeScreen(viewModel: HogeViewModel) { ... Column { ... HogeScreen( onItemClick = { viewModel.dispatchViewEvent(HogeViewModel.ViewEvent.ClickItem) } ) } } @Composable fun HogeScreen( onItemClick: () -> Unit ) { Column { ... Item(onItemClick) } } 下層のComposableからはViewEventを意識しない Composableの再利用性が上がる

Slide 49

Slide 49 text

© ZOZO Technologies, Inc. 49 Composable Composable Composable Composable ViewModel Event発生 ViewEventの発行からUIの更新まで

Slide 50

Slide 50 text

© ZOZO Technologies, Inc. 50 Composable Composable Composable Composable ViewModel Event発生 ViewEventの発行からUIの更新まで

Slide 51

Slide 51 text

© ZOZO Technologies, Inc. 51 Composable Composable Composable Composable ViewModel Event発生 ViewEventの発行からUIの更新まで

Slide 52

Slide 52 text

© ZOZO Technologies, Inc. 52 Composable Composable Composable Composable ViewModel Event発生 ViewEventを発行 ViewEventの発行からUIの更新まで

Slide 53

Slide 53 text

© ZOZO Technologies, Inc. 53 Composable Composable Composable Composable ViewModel Event発生 Stateを更新 ViewEventの発行からUIの更新まで State

Slide 54

Slide 54 text

© ZOZO Technologies, Inc. 54 ViewEventの発行からUIの更新まで Composable Composable Composable Composable ViewModel Flow State ViewState 画面全体の状態を通知

Slide 55

Slide 55 text

© ZOZO Technologies, Inc. 55 ViewEventの発行からUIの更新まで Composable Composable Composable Composable ViewModel State ViewState ViewData

Slide 56

Slide 56 text

© ZOZO Technologies, Inc. 56 ViewEventの発行からUIの更新まで Composable Composable Composable Composable ViewModel State ViewState 表示するデータ 差分更新 差分更新 表示するデータ

Slide 57

Slide 57 text

© ZOZO Technologies, Inc. 57 ZOZOTOWNへのCompose導入の課題 ✅ 既存のZOZOTOWNのUIの状態管理方法がComposeに適していない ○ UIの状態管理・Eventの処理方法を見直す必要があった ● 無秩序なComposable作成によるComposableの再利用性・可読性低下 ○ チームでの開発に向けてComposableの設計ルールを制定する必要があった

Slide 58

Slide 58 text

© ZOZO Technologies, Inc. 58 Composable設計ルールの制定 無秩序なComposable作成によるComposableの再利用性・可読性低下 ● Reactなど、Webフロントの宣言的UIフレームワークでの事例を調査 ○ Atomic Designを使ってReactコンポーネントを再設計した話, スペースマーケットブログ ○ Atomic Design ベースの Vue コンポーネント設計, Qiita ● チームでの開発に備え、Composableの設計ルールを検討

Slide 59

Slide 59 text

© ZOZO Technologies, Inc. 59 AtomicDesign ● 主にWebフロントの世界で用いられるデザインシステム ○ Amazon.co.jp: Atomic Design ~堅牢で使いやすいUIを効率良く設計する : 五藤 佑典: Japanese Books ● 堅牢性の高いUI実装を実現することができる ● UIの要素(コンポーネント)をAtoms, Molecules, Organisms, Templates, Pagesに分類し、 階層化する Atomic Design | Brad Frost

Slide 60

Slide 60 text

© ZOZO Technologies, Inc. 60 AtomicDesign Pages Templates Organisms Molecules Atoms

Slide 61

Slide 61 text

© ZOZO Technologies, Inc. 61 AtomicDesign Pages Templates Organisms Molecules Atoms 下層のコンポーネントは上層のコンポーネントに依存不可

Slide 62

Slide 62 text

© ZOZO Technologies, Inc. 62 Atoms 機能的に分割できる最小のUI要素 ● ZOZOTOWN Androidでは ○ Text, Button, Row, ColumnなどComposeで提供されて いるComposable ○ 吹き出しやアイコンなど、独自に作成したComposableの うちそれ以上分割できないもの ● 命名規則 ○ すべてのアプリケーション利用できるように、可能な限り抽 象的な名前とする

Slide 63

Slide 63 text

© ZOZO Technologies, Inc. 63 Molecules ユーザーに対して目的・動機を提供する最小のUI要素 ● ZOZOTOWN Androidでは ○ TextやRowなどのAtomsを組み合わせて作成した Composable ● 命名規則 ○ ユーザーが何を実現したいのかを表す名前とする

Slide 64

Slide 64 text

© ZOZO Technologies, Inc. 64 Organisms 独立して成立するコンテンツ ● ZOZOTOWN Androidでは ○ Atoms, Molecules, Organismsを組み合わせて作成 したComposable ● 命名規則 ○ 情報自体を表す名前とする

Slide 65

Slide 65 text

© ZOZO Technologies, Inc. 65 Templates ページのコンテンツ構造を明確にするオブジェクト ● ZOZOTOWN Androidでは ○ 画面全体のレイアウトを管理し、各UI要素に必要な ViewDataを分配する ○ 1画面に対して1つ作成する ● 命名規則 ○ [画面名]Screen

Slide 66

Slide 66 text

© ZOZO Technologies, Inc. 66 Pages 実際のデータを画面に反映するオブジェクト ● ZOZOTOWN Androidでは ○ ViewModelへの依存(動的なデータへの関心)を持つ ○ ViewModelにViewEventを発行する ○ Templatesに実際のデータを提供する ● 命名規則 ○ [画面名]Screen

Slide 67

Slide 67 text

© ZOZO Technologies, Inc. 67 ZOZOTOWN Androidでの例(デバッグメニュー) Atoms Molecules Organisms Text, Button AccessPointSpinner AccessPoint Templates, Pages DebugMenuScreen

Slide 68

Slide 68 text

© ZOZO Technologies, Inc. 68 ZOZOTOWN AndroidへのCompose導入の課題 ✅ 既存のZOZOTOWNのUIの状態管理方法がComposeに適していない ○ UIの状態管理・Eventの処理方法を見直す必要があった ✅ 無秩序なComposable作成によるComposableの再利用性・可読性低下 ○ チームでの開発に向けてComposableの設計ルールを制定する必要があった

Slide 69

Slide 69 text

© ZOZO Technologies, Inc. 69 まとめ

Slide 70

Slide 70 text

© ZOZO Technologies, Inc. 70 まとめ ● ZOZOTOWN AndroidへJetpack Composeを導入した ● Compose導入のための課題を洗い出し、解決した ○ UIの状態管理・Eventの処理方法を見直した ○ AtomicDesignをベースにしたComposable設計ルールを制定した

Slide 71

Slide 71 text

© ZOZO Technologies, Inc. 71 今後の課題 ● UIの状態管理方法のルール化 ○ チームでスムーズに開発を行うため、今回の検討内容をルールに落とし込む ● より複雑な画面でのStateの扱い ○ 今回検討した設計ではカバーできないユースケースへの対応 ● Composable設計ルールのブラッシュアップ ○ 実際にチームで運用し、よりZOZOTOWNに適した形にする ● デザイナー・エンジニア間のユビキタス言語としてのAtomicDesignの活用 ○ デザインに関するコミュニケーションをよりスムーズにする

Slide 72

Slide 72 text

© ZOZO Technologies, Inc. 72 今後の課題 → 実装・検証を繰り返して改善する ● UIの状態管理方法のルール化 ○ チームでスムーズに開発を行うため、今回の検討内容をルールに落とし込む ● より複雑な画面でのStateの扱い ○ 今回検討した設計ではカバーできないユースケースへの対応 ● Composable設計ルールのブラッシュアップ ○ 実際にチームで運用し、よりZOZOTOWNに適した形にする ● デザイナー・エンジニア間のユビキタス言語としてのAtomicDesignの活用 ○ デザインに関するコミュニケーションをよりスムーズにする

Slide 73

Slide 73 text

© ZOZO Technologies, Inc. 73 最後に ● ZOZOテクノロジーズではAndroidエンジニアを募集しています ● 興味のある方はぜひお声がけください!カジュアル面談のご応募お待ちしております ○ カジュアル面談応募フォーム ■ https://hrmos.co/pages/zozotech/jobs/0000193

Slide 74

Slide 74 text

No content