2021年8月23日月曜日

Jetpack Compose + ViewModel + Navigation + Hilt + AssistedInject

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


ViewModelExt.kt @Composable inline fun <reified VM : ViewModel> assistedViewModel( viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) { "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner" }, crossinline viewModelProducer: (SavedStateHandle) -> VM ): VM { val factory = if (viewModelStoreOwner is NavBackStackEntry) { object : AbstractSavedStateViewModelFactory(viewModelStoreOwner, viewModelStoreOwner.arguments) { @Suppress("UNCHECKED_CAST") override fun <T : ViewModel?> create(key: String, modelClass: Class<T>, handle: SavedStateHandle): T { return viewModelProducer(handle) as T } } } else { // Use the default factory provided by the ViewModelStoreOwner // and assume it is an @AndroidEntryPoint annotated fragment or activity null } return viewModel(viewModelStoreOwner, factory = factory) } fun Context.extractActivity(): Activity { var ctx = this while (ctx is ContextWrapper) { if (ctx is Activity) { return ctx } ctx = ctx.baseContext } throw IllegalStateException( "Expected an activity context for creating a HiltViewModelFactory for a " + "NavBackStackEntry but instead found: $ctx" ) } MainActivity.kt @HiltAndroidApp class MyApplication : Application() @AndroidEntryPoint class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MaterialTheme { Surface(color = MaterialTheme.colors.background) { MyApp() } } } } } @Composable fun MyApp() { val navController = rememberNavController() NavHost(navController, startDestination = "Screen1") { composable("Screen1") { LazyColumn(modifier = Modifier.fillMaxSize()) { items(20) { Text( text = "Item : $it", modifier = Modifier .fillMaxWidth() .clickable { navController.navigate("Screen2/$it") } .padding(16.dp) ) } } } composable( route = "Screen2/{id}", arguments = listOf( navArgument("id") { type = NavType.IntType }, ) ) { val arguments = requireNotNull(it.arguments) val id = arguments.getInt("id") val viewModel = assistedViewModel { savedStateHandle -> Screen2ViewModel.provideFactory(LocalContext.current) .create(savedStateHandle, id) } Screen2(viewModel) } } } @Composable fun Screen2(viewModel: Screen2ViewModel) { Text( text = viewModel.greet(), modifier = Modifier.padding(24.dp) ) } class Screen2ViewModel @AssistedInject constructor( private val nameProvider: NameProvider, @Assisted private val savedStateHandle: SavedStateHandle, @Assisted private val id: Int ) : ViewModel() { @AssistedFactory interface Factory { fun create(savedStateHandle: SavedStateHandle, id: Int): Screen2ViewModel } @EntryPoint @InstallIn(ActivityComponent::class) interface ActivityCreatorEntryPoint { fun getScreen2ViewModelFactory(): Factory } companion object { fun provideFactory(context: Context): Factory { val activity = context.extractActivity() return EntryPoints.get(activity, ActivityCreatorEntryPoint::class.java) .getScreen2ViewModelFactory() } } fun greet(): String { return "Hello ${nameProvider.name()} : id = $id" } } @Singleton class NameProvider @Inject constructor() { fun name(): String { return "Android" } } Issue (https://github.com/google/dagger/issues/2287) は 1月からあるけど、進んでなさそう。


0 件のコメント:

コメントを投稿