2021年8月23日月曜日

Jetpack Compose + ViewModel + Navigation + Hilt + AssistedInject

Jetpack Compose + ViewModel + Navigation + Hilt の ViewModel で AssistedInject を使う方法。


ViewModelExt.kt
  1. @Composable  
  2. inline fun <reified VM : ViewModel> assistedViewModel(  
  3.     viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) {  
  4.         "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner"  
  5.     },  
  6.     crossinline viewModelProducer: (SavedStateHandle) -> VM  
  7. ): VM {  
  8.     val factory = if (viewModelStoreOwner is NavBackStackEntry) {  
  9.         object : AbstractSavedStateViewModelFactory(viewModelStoreOwner, viewModelStoreOwner.arguments) {  
  10.             @Suppress("UNCHECKED_CAST")  
  11.             override fun <T : ViewModel?> create(key: String, modelClass: Class<T>, handle: SavedStateHandle): T {  
  12.                 return viewModelProducer(handle) as T  
  13.             }  
  14.         }  
  15.     } else {  
  16.         // Use the default factory provided by the ViewModelStoreOwner  
  17.         // and assume it is an @AndroidEntryPoint annotated fragment or activity  
  18.         null  
  19.     }  
  20.     return viewModel(viewModelStoreOwner, factory = factory)  
  21. }  
  22.   
  23. fun Context.extractActivity(): Activity {  
  24.     var ctx = this  
  25.     while (ctx is ContextWrapper) {  
  26.         if (ctx is Activity) {  
  27.             return ctx  
  28.         }  
  29.         ctx = ctx.baseContext  
  30.     }  
  31.     throw IllegalStateException(  
  32.         "Expected an activity context for creating a HiltViewModelFactory for a " +  
  33.                 "NavBackStackEntry but instead found: $ctx"  
  34.     )  
  35. }  
MainActivity.kt
  1. @HiltAndroidApp  
  2. class MyApplication : Application()  
  3.   
  4. @AndroidEntryPoint  
  5. class MainActivity : ComponentActivity() {  
  6.     override fun onCreate(savedInstanceState: Bundle?) {  
  7.         super.onCreate(savedInstanceState)  
  8.         setContent {  
  9.             MaterialTheme {  
  10.                 Surface(color = MaterialTheme.colors.background) {  
  11.                     MyApp()  
  12.                 }  
  13.             }  
  14.         }  
  15.     }  
  16. }  
  17.   
  18. @Composable  
  19. fun MyApp() {  
  20.     val navController = rememberNavController()  
  21.   
  22.     NavHost(navController, startDestination = "Screen1") {  
  23.   
  24.         composable("Screen1") {  
  25.             LazyColumn(modifier = Modifier.fillMaxSize()) {  
  26.                 items(20) {  
  27.                     Text(  
  28.                         text = "Item : $it",  
  29.                         modifier = Modifier  
  30.                             .fillMaxWidth()  
  31.                             .clickable {  
  32.                                 navController.navigate("Screen2/$it")  
  33.                             }  
  34.                             .padding(16.dp)  
  35.                     )  
  36.                 }  
  37.             }  
  38.         }  
  39.   
  40.         composable(  
  41.             route = "Screen2/{id}",  
  42.             arguments = listOf(  
  43.                 navArgument("id") { type = NavType.IntType },  
  44.             )  
  45.         ) {  
  46.             val arguments = requireNotNull(it.arguments)  
  47.             val id = arguments.getInt("id")  
  48.             val viewModel = assistedViewModel { savedStateHandle ->  
  49.                 Screen2ViewModel.provideFactory(LocalContext.current)  
  50.                     .create(savedStateHandle, id)  
  51.             }  
  52.   
  53.             Screen2(viewModel)  
  54.         }  
  55.     }  
  56. }  
  57.   
  58. @Composable  
  59. fun Screen2(viewModel: Screen2ViewModel) {  
  60.     Text(  
  61.         text = viewModel.greet(),  
  62.         modifier = Modifier.padding(24.dp)  
  63.     )  
  64. }  
  65.   
  66. class Screen2ViewModel @AssistedInject constructor(  
  67.     private val nameProvider: NameProvider,  
  68.     @Assisted private val savedStateHandle: SavedStateHandle,  
  69.     @Assisted private val id: Int  
  70. ) : ViewModel() {  
  71.   
  72.     @AssistedFactory  
  73.     interface Factory {  
  74.         fun create(savedStateHandle: SavedStateHandle, id: Int): Screen2ViewModel  
  75.     }  
  76.   
  77.     @EntryPoint  
  78.     @InstallIn(ActivityComponent::class)  
  79.     interface ActivityCreatorEntryPoint {  
  80.         fun getScreen2ViewModelFactory(): Factory  
  81.     }  
  82.   
  83.     companion object {  
  84.         fun provideFactory(context: Context): Factory {  
  85.             val activity = context.extractActivity()  
  86.             return EntryPoints.get(activity, ActivityCreatorEntryPoint::class.java)  
  87.                 .getScreen2ViewModelFactory()  
  88.         }  
  89.     }  
  90.   
  91.     fun greet(): String {  
  92.         return "Hello ${nameProvider.name()} : id = $id"  
  93.     }  
  94. }  
  95.   
  96. @Singleton  
  97. class NameProvider @Inject constructor() {  
  98.   
  99.     fun name(): String {  
  100.         return "Android"  
  101.     }  
  102. }  
Issue (https://github.com/google/dagger/issues/2287) は 1月からあるけど、進んでなさそう。


0 件のコメント:

コメントを投稿