Estoy tratando de agregar Dagger 2 a mi proyecto. Pude inyectar ViewModels (componente de Arquitectura AndroidX) para mis fragmentos.
Tengo un ViewPager que tiene 2 instancias del mismo fragmento (solo un cambio menor para cada pestaña) y en cada pestaña, estoy observando una LiveDataactualización sobre el cambio de datos (desde la API).
El problema es que cuando llega la respuesta de la API y la actualiza LiveData, los mismos datos en el fragmento actualmente visible se envían a los observadores en todas las pestañas. (Creo que esto es probablemente debido al alcance de la ViewModel).
Así es como estoy observando mis datos:
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
activityViewModel.expenseList.observe(this, Observer {
swipeToRefreshLayout.isRefreshing = false
viewAdapter.setData(it)
})
....
}
Estoy usando esta clase para proporcionar ViewModels:
class ViewModelProviderFactory @Inject constructor(creators: MutableMap<Class<out ViewModel?>?, Provider<ViewModel?>?>?) :
ViewModelProvider.Factory {
private val creators: MutableMap<Class<out ViewModel?>?, Provider<ViewModel?>?>? = creators
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
var creator: Provider<out ViewModel?>? = creators!![modelClass]
if (creator == null) { // if the viewmodel has not been created
// loop through the allowable keys (aka allowed classes with the @ViewModelKey)
for (entry in creators.entries) { // if it's allowed, set the Provider<ViewModel>
if (modelClass.isAssignableFrom(entry.key!!)) {
creator = entry.value
break
}
}
}
// if this is not one of the allowed keys, throw exception
requireNotNull(creator) { "unknown model class $modelClass" }
// return the Provider
return try {
creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
companion object {
private val TAG: String? = "ViewModelProviderFactor"
}
}
Estoy vinculando mi ViewModelasí:
@Module
abstract class ActivityViewModelModule {
@MainScope
@Binds
@IntoMap
@ViewModelKey(ActivityViewModel::class)
abstract fun bindActivityViewModel(viewModel: ActivityViewModel): ViewModel
}
Estoy usando @ContributesAndroidInjectorpara mi fragmento así:
@Module
abstract class MainFragmentBuildersModule {
@ContributesAndroidInjector
abstract fun contributeActivityFragment(): ActivityFragment
}
Y estoy agregando estos módulos a mi MainActivitysubcomponente así:
@Module
abstract class ActivityBuilderModule {
...
@ContributesAndroidInjector(
modules = [MainViewModelModule::class, ActivityViewModelModule::class,
AuthModule::class, MainFragmentBuildersModule::class]
)
abstract fun contributeMainActivity(): MainActivity
}
Aquí está mi AppComponent:
@Singleton
@Component(
modules =
[AndroidSupportInjectionModule::class,
ActivityBuilderModule::class,
ViewModelFactoryModule::class,
AppModule::class]
)
interface AppComponent : AndroidInjector<SpenmoApplication> {
@Component.Builder
interface Builder {
@BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
}
Estoy extendiendo DaggerFragmente inyectando ViewModelProviderFactoryasí:
@Inject
lateinit var viewModelFactory: ViewModelProviderFactory
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
....
activityViewModel =
ViewModelProviders.of(this, viewModelFactory).get(key, ActivityViewModel::class.java)
activityViewModel.restartFetch(hasReceipt)
}
El keyserá diferente para ambos fragmentos.
¿Cómo puedo asegurarme de que solo el observador del fragmento actual se esté actualizando?
EDITAR 1 ->
He agregado un proyecto de muestra con el error. Parece que el problema ocurre solo cuando se agrega un ámbito personalizado. Consulte el proyecto de muestra aquí: enlace de Github
masterBranch tiene la aplicación con el problema. Si actualiza cualquier pestaña (deslice para actualizar), el valor actualizado se refleja en ambas pestañas. Esto solo sucede cuando le agrego un alcance personalizado ( @MainScope).
working_fine Branch tiene la misma aplicación sin alcance personalizado y funciona bien.
Avíseme si la pregunta no está clara.
fuente

working_finerama? ¿Por qué necesitas el alcance?Respuestas:
Quiero recapitular la pregunta original, aquí está:
Según tengo entendido, usted tiene la impresión de que solo porque está tratando de obtener una instancia de
ViewModeluso de diferentes claves, entonces debería recibir diferentes instancias deViewModel:La realidad es un poco diferente. Si coloca el siguiente fragmento de inicio de sesión, verá que esos dos fragmentos están utilizando exactamente la misma instancia de
PagerItemViewModel:Vamos a sumergirnos y entender por qué sucede esto.
ViewModelProvider#get()Intentará obtener internamente una instancia dePagerItemViewModelaViewModelStoreque básicamente es un mapa deStringaViewModel.Cuando
FirstFragmentsolicita una instancia dePagerItemViewModellamapestá vacía, pormFactory.create(modelClass)lo tanto, se ejecuta, lo que termina enViewModelProviderFactory.creator.get()termina llamandoDoubleCheckcon el siguiente código:El
instancees ahoranull, por lo tanto,PagerItemViewModelse crea una nueva instancia de y se guarda eninstance(ver // 2).Ahora ocurre exactamente el mismo procedimiento para
SecondFragment:PagerItemViewModelmapahora no está vacío, pero no contiene una instancia dePagerItemViewModelcon clavefalsePagerItemViewModelse inicia una nueva instancia de a través demFactory.create(modelClass)ViewModelProviderFactoryejecución interna alcanzacreator.get()cuya implementación esDoubleCheckAhora, el momento clave. Esta
DoubleCheckes la misma instancia deDoubleCheckque se utilizó para la creación deViewModelejemplo, cuandoFirstFragmentse le preguntó por ello. ¿Por qué es la misma instancia? Porque has aplicado un alcance al método del proveedor.El
if (result == UNINITIALIZED)(// 1) se evalúa como falso y exactamente la misma instancia deViewModelse devuelve al llamador -SecondFragment.Ahora, ambos fragmentos están utilizando la misma instancia,
ViewModelpor lo tanto, está perfectamente bien que muestren los mismos datos.fuente
ViewModelse crea con el ciclo de vida de la actividad / fragmento y se destruye tan pronto como se destruye su ciclo de vida. No debe administrar el ciclo de vida / destrucción de creación de ViewModel por sí mismo, eso es lo que los componentes de arquitectura están haciendo por usted como cliente de esa API.Ambos fragmentos reciben la actualización de livedata porque el visor mantiene ambos fragmentos en estado reanudado. Dado que necesita la actualización solo en el fragmento actual visible en el visor, la actividad del host define el contexto del fragmento actual , la actividad debe dirigir explícitamente las actualizaciones al fragmento deseado.
Debe mantener un mapa de Fragmento a LiveData que contenga entradas para todos los fragmentos (asegúrese de tener un identificador que pueda diferenciar dos instancias de fragmento del mismo fragmento) agregado al visor.
Ahora la actividad tendrá un MediatorLiveData observando los livedata originales observados directamente por los fragmentos. Cada vez que el livedata original publique una actualización, se entregará a mediatorLivedata y el mediatorlivedata en turen solo publicará el valor en livedata del fragmento seleccionado actual. Estos datos en vivo se recuperarán del mapa de arriba.
El código impl se vería así:
fuente
FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)entonces, ¿cómo es que el visor mantiene ambos fragmentos en estado reanudado? Esto no estaba sucediendo antes de agregar la daga 2 al proyecto.