Slide 1

Slide 1 text

Hilt の Custom Component について Mobile Act OSAKA 13 @takuji31

Slide 2

Slide 2 text

自己紹介 西林 拓志 (にしばやし たくじ ) Twitter/GitHub takuji31 Sansan 株式会社 技術本部 Mobile Application グループ Eight Android エンジニア Android (2009〜 ) Kotlin (2014〜 ) 1

Slide 3

Slide 3 text

Hilt 使ってますか? 2

Slide 4

Slide 4 text

Hilt のあまり知られてないであろう Custom Component について紹介します 3

Slide 5

Slide 5 text

AGENDA Custom Component とは Custom Component の使い方 注意点 4

Slide 6

Slide 6 text

Custom Component とは 5

Slide 7

Slide 7 text

Hilt の標準 Component Tree の上に別の Component を 定義する機能 6

Slide 8

Slide 8 text

Component Tree SingletonComponent ActivityRetainedComponent ServiceComponent ActivityComponent ViewModelComponent FragmentComponent ViewComponent ViewWithFragmentComponent 7

Slide 9

Slide 9 text

Component Tree with Custom Component SingletonComponent ActivityRetainedComponent ServiceComponent UserComponent ActivityComponent ViewModelComponent FragmentComponent ViewComponent HogeComponent ViewWithFragmentComponent 8

Slide 10

Slide 10 text

Hilt 標準のライフサイクル外でコンポーネントを用意した い時に使える 9

Slide 11

Slide 11 text

使い方 10

Slide 12

Slide 12 text

使い方 Component をつくる Builder をつくる EntryPoint をつくる ComponentManager をつくる 依存を取得 11

Slide 13

Slide 13 text

Component をつくる 12

Slide 14

Slide 14 text

Component をつくる @Scope annotation class UserScope //スコープ用のアノテーションを作っておく @UserScope @DefineComponent(parent = SingletonComponent::class) // 親のComponent指定 interface UserComponent 13

Slide 15

Slide 15 text

Builder をつくる 14

Slide 16

Slide 16 text

Builder をつくる @DefineComponent.Builder interface UserComponentBuilder { // 特定のインスタンスをバインドしたい時。戻り値はBuilderの型。 fun userId(@BindsInstance userId: UserId): UserComponentBuilder fun build(): UserComponent //ビルド用メソッドは必須 } 15

Slide 17

Slide 17 text

EntryPoint をつくる 16

Slide 18

Slide 18 text

EntryPoint? 17

Slide 19

Slide 19 text

Hilt では Component との過度な依存関係を避けるために Component に直接依存取得や注入のメソッドを定義でき ない 18

Slide 20

Slide 20 text

依存を取得するためには EntryPoint の定義が必須 19

Slide 21

Slide 21 text

EntryPoint をつくる @EntryPoint @InstallIn(UserComponent::class) interface UserComponentEntryPoint { fun counterRepository(): CounterRepository // 依存を取得するメソッド } 20

Slide 22

Slide 22 text

ComponentManager をつくる 21

Slide 23

Slide 23 text

ComponentManager? 22

Slide 24

Slide 24 text

Builder から Component を生成して管理するやつ 23

Slide 25

Slide 25 text

親の Component のスコープシングルトンにしておくとよ さそう 24

Slide 26

Slide 26 text

ComponentManager @Singleton class UserComponentManager @Inject constructor( private val componentBuilder: Provider, ) { private var component: UserComponent = componentBuilder.get().userId(UserId(1)).build() private val entryPoint get() = EntryPoints.get(component, UserComponentEntryPoint::class.java) val counterRepository: CounterRepository get() = entryPoint.counterRepository() } 25

Slide 27

Slide 27 text

依存を取得 26

Slide 28

Slide 28 text

Custom Component の依存を ViewModel や Activity か らどうやってとる …? 27

Slide 29

Slide 29 text

Component Tree with Custom Component SingletonComponent ActivityRetainedComponent ServiceComponent UserComponent ActivityComponent ViewModelComponent FragmentComponent ViewComponent HogeComponent ViewWithFragmentComponent 28

Slide 30

Slide 30 text

どうやって依存を取得するのか とれない とれない SingletonComponent ActivityRetainedComponent ServiceComponent UserComponent ActivityComponent ViewModelComponent FragmentComponent ViewComponent ViewWithFragmentComponent 29

Slide 31

Slide 31 text

ComponentManager からとる 30

Slide 32

Slide 32 text

ComponentManager からとる @Module @InstallIn(ViewModelComponent::class) class ViewModelModule { @Provides fun provideCurrentUserCounterRepository( userComponentManager: UserComponentManager ): CounterRepository = userComponentManager.counterRepository } 31

Slide 33

Slide 33 text

32

Slide 34

Slide 34 text

注意点 33

Slide 35

Slide 35 text

ライフサイクルの非一致によって依存がズレる可能性 34

Slide 36

Slide 36 text

ex. UserComponent はユーザーが切り替わったらリセッ トされる 35

Slide 37

Slide 37 text

切り替わり前 SingletonComponent ActivityRetainedComponent UserComponent ViewModelComponent 36

Slide 38

Slide 38 text

切り替わり前 SingletonComponent ActivityRetainedComponent UserComponent2 ViewModelComponent 37

Slide 39

Slide 39 text

ViewModel が保持している UserComponent からの依存 が UserComponent2 のものに置き換わらない 38

Slide 40

Slide 40 text

対策 39

Slide 41

Slide 41 text

Provider を使って毎回取得 40

Slide 42

Slide 42 text

Provider#get を呼ぶ度に ComponentManager から 最新のインスタンスを取得できる 41

Slide 43

Slide 43 text

Provider を使って毎回取得 @HiltViewModel class MainViewModel @Inject constructor( private val currentUserCounterRepository: CounterRepository, ) 42

Slide 44

Slide 44 text

Provider を使って毎回取得 @HiltViewModel class MainViewModel @Inject constructor( private val currentUserCounterRepository: Provider, ) 43

Slide 45

Slide 45 text

他には変更がある度に Activity を立ち上げなおす、といっ た富豪的なやり方もある 44

Slide 46

Slide 46 text

そもそも Custom Component を使うべきなのか? 45

Slide 47

Slide 47 text

Custom Component ドキュメントの 1 行目 46

Slide 48

Slide 48 text

47

Slide 49

Slide 49 text

48

Slide 50

Slide 50 text

ドキュメント曰く However, before creating a custom component, consider if you really need one as not every place where you can logically add a custom component deserves one. カスタム コンポーネントを作成する前に、カスタム コンポーネントが本当に必要かどう かを検討してください。カスタム コンポーネントを論理的に追加できるすべての場所に カスタム コンポーネントが必要なわけではないためです。 49

Slide 51

Slide 51 text

Custom Component の欠点 Each component/scope adds cognitive overhead. They can complicate the graph with combinatorics (e.g. if the component is a child of the ViewComponent conceptually, two components likely need to be added for ViewComponent and ViewWithFragmentComponent). Components can have only one parent. The component hierarchy can’t form a diamond. Creating more components increases the likelihood of getting into a situation where a diamond dependency is needed. Unfortunately, there is no good solution to this diamond problem and it can be difficult to predict and avoid. Custom components work against standardization. The more custom components are used, the harder it is for shared libraries. 50

Slide 52

Slide 52 text

Custom Component の欠点 各コンポーネント /スコープは認知オーバーヘッドを追加します。 組み合わせ論によってグラフが複雑になる可能性があります (たとえば、コンポーネント が概念的に の子である場合、と ViewComponent に 2 つのコンポーネントを追加する必 要がある可能性があります )。 ViewComponentViewWithFragmentComponent コンポーネントは親を 1 つしか持つことができません。コンポーネント階層はダイヤモ ンドを形成できません。コンポーネントをさらに作成すると、ダイヤモンド依存関係が 必要になる状況になる可能性が高くなります。残念ながら、このダイヤモンド問題に対 する適切な解決策はなく、予測して回避することは困難です。 カスタム コンポーネントは標準化に反します。カスタム コンポーネントが使用されるほ ど、共有ライブラリが難しくなります。 51

Slide 53

Slide 53 text

Custom Component を使うべきかよく検討しましょう 52

Slide 54

Slide 54 text

ユーザースコープ的なものは Custom Component 以外 でも対応できる 53

Slide 55

Slide 55 text

たとえば アプリ内のユーザーを通知する Flow を Repository から取れるようにする Single Activity で ViewModelComponent で保持する データ取得の度に毎回ユーザー情報を Room の DB やメモリーから取得する etc. 54

Slide 56

Slide 56 text

Hilt(Dagger)は難しいと言われがちなので、 Hilt 上で難し いことをやるより実装側でなんとかした方が個々の複雑さ は下がる 55

Slide 57

Slide 57 text

https://dagger.dev/hilt/custom-components 56

Slide 58

Slide 58 text

ご利用は計画的に 57