首页 > 解决方案 > LiveData - 为什么在我 postValue 时不调用 onChanged?

问题描述

我正在使用 Kotlin、Coroutine、LiveData 开发一个 Android 应用程序。当我导航到另一个片段时,例如,导航到底部导航视图的第二个选项卡并返回到前一个片段时,永远不会调用onChanged方法。Observer

编辑 我发现了问题。当我持有像 velow 这样的视图实例时会出现问题:\

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        if (rootView == null) {
            rootView = inflater.inflate(layoutRes, container, false)
        }
        return rootView
    }

但是当我不保留它时,就会出现问题,如下所示:

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? = inflater.inflate(layoutRes, container, false)

搜索虚拟机:

class SearchVM @Inject constructor(
    private val docksRepository: DocksRepository,
    app: Application
) : AndroidViewModel(app) {
    val docks: LiveData<List<Dock>> = MutableLiveData()

    val dockStart: LiveData<Dock> = MutableLiveData()
    val dockEnd: LiveData<Dock> = MutableLiveData()
    val dateStart: LiveData<Calendar> = MutableLiveData()
    val dateEnd: LiveData<Calendar> = MutableLiveData()

    init {
        viewModelScope.launch(IO) {
            try {
                val response = docksRepository.getDocks()
                docks as MutableLiveData<List<Dock>>
                docks.postValue(response)
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }

    fun setDockStart(dock: Dock) {
        dockStart as MutableLiveData<Dock>
        dockStart.postValue(dock)
    }

    fun setDockEnd(dock: Dock) {
        dockEnd as MutableLiveData<Dock>
        dockEnd.postValue(dock)
    }

    fun setDateStart(calendar: Calendar) {
        dateStart as MutableLiveData<Calendar>
        dateStart.postValue(calendar)
    }

    fun setDateEnd(calendar: Calendar) {
        dateEnd as MutableLiveData<Calendar>
        dateEnd.postValue(calendar)
    }
}

基本片段:

@SuppressLint("Registered")
abstract class BaseFragment<T : AndroidViewModel>(
    @LayoutRes private val layoutRes: Int,
    private val viewModel: Class<T>
) : DaggerFragment() {

    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory
    var rootView: View? = null
    lateinit var vm: T

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        if (rootView == null) {
            rootView = inflater.inflate(layoutRes, container, false)
        }
        return rootView
    }

    override fun onAttach(context: Context) {
        super.onAttach(context)
        vm = ViewModelProviders.of(requireActivity(), viewModelFactory)[viewModel]
    }

    protected fun hideBottomNavigationView() {
        (activity as MainActivity).hideBottomNav()
    }

    protected fun showBottomNavigationView() {
        (activity as MainActivity).showBottomNav()
    }

搜索片段:

  
class SearchFragment : BaseFragment<SearchVM>(R.layout.fragment_serach, SearchVM::class.java) {

    private val now = Calendar.getInstance()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        Picasso.get().load("https://imgurl.ir/uploads/t326248_.png").into(img_background)

        vm.dateStart.observe(viewLifecycleOwner, Observer {
            tv_startTime.text = it.getDisplayText()
        })
        vm.dateEnd.observe(viewLifecycleOwner, Observer {
            tv_endTime.text = it.getDisplayText()
        })

        vm.dockStart.observe(viewLifecycleOwner, Observer {
            tv_source.text = it.name
        })
        vm.dockEnd.observe(viewLifecycleOwner, Observer {
            tv_destination.text = it.name
        })

        tv_source.setOnClickListener {
            vm.docks.value?.let {
                showDockPickerDialog(it, vm::setDockStart)
            }
        }

        tv_destination.setOnClickListener {
            vm.docks.value?.let {
                showDockPickerDialog(it, vm::setDockEnd)
            }
        }

        tv_startTime.setOnClickListener {
            showDatePicker(vm.dateStart.value ?: now, vm::setDateStart)
        }

        tv_endTime.setOnClickListener {
            showDatePicker(vm.dateEnd.value ?: now, vm::setDateEnd)
        }

        imageButton.setOnClickListener {
            val dockStart = vm.dockStart.value
                ?: return@setOnClickListener toast(R.string.search_fragment_validation_error_fill_dock_start)
            val dockEnd = vm.dockEnd.value
                ?: return@setOnClickListener toast(R.string.search_fragment_validation_error_fill_dock_end)
            val dateStart = vm.dateStart.value
                ?: return@setOnClickListener toast(R.string.search_fragment_validation_error_fill_date_start)
            val dateEnd = vm.dateEnd.value
                ?: return@setOnClickListener toast(R.string.search_fragment_validation_error_fill_date_end)
            val action = HomeFragmentDirections.actionHomeFragmentToSearchResultFragment(
                dockStart,
                dockEnd,
                dateStart,
                dateEnd
            )
            view.findNavController()
                .navigate(action)
        }
    }

    private fun showDatePicker(now: Calendar, callBack: (date: Calendar) -> Unit) {
        val datePickers = DatePickerDialog(
            requireContext(),
            DatePickerDialog.OnDateSetListener { view, year, month, dayOfMonth ->
                val selectedDate = Calendar.getInstance()
                selectedDate.set(Calendar.YEAR, year)
                selectedDate.set(Calendar.MONTH, month)
                selectedDate.set(Calendar.DAY_OF_MONTH, dayOfMonth)
                callBack.invoke(selectedDate)
            },
            now.get(Calendar.YEAR), now.get(Calendar.MONTH), now.get(Calendar.DAY_OF_MONTH)
        )
        datePickers.show()
    }

    private fun showDockPickerDialog(docks: List<Dock>, callback: (dock: Dock) -> Unit) {
        val adapter = ArrayAdapter(requireActivity(), android.R.layout.simple_list_item_1, docks)
        AlertDialog.Builder(requireContext())
            .setAdapter(adapter) { DialogInterface, i ->
                DialogInterface.dismiss()
                callback.invoke(docks[i])
            }.show()
    }
}

ViewModelModule.kt


@Module
abstract class ViewModelModule {
    // Bind ViewModels Here ...

    @Binds
    @IntoMap
    @ViewModelKey(SearchVM::class)
    internal abstract fun searchVM(viewModel: SearchVM): ViewModel
}

@Target(
    AnnotationTarget.FUNCTION,
    AnnotationTarget.PROPERTY_GETTER,
    AnnotationTarget.PROPERTY_SETTER
)
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
@MapKey
internal annotation class ViewModelKey(val value: KClass<out ViewModel>)

class ViewModelFactory @Inject constructor(
    private val viewModels: MutableMap<Class<out ViewModel>, Provider<ViewModel>>
) : ViewModelProvider.Factory {
    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>): T = viewModels[modelClass]?.get() as T
}

@Module
abstract class ViewModelFactoryModule {
    @Binds
    internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
}

应用组件.kt

@Singleton
@Component(
    modules = [
        ViewModelModule::class,
        ViewModelFactoryModule::class
    ]
)
interface AppComponent : AndroidInjector<App> {

    @Component.Builder
    interface Builder {

        @BindsInstance
        fun application(application: Application): Builder

        fun build(): AppComponent
    }

}

基本活动.kt

@SuppressLint("Registered")
abstract class BaseActivity<T : AndroidViewModel>(private val viewModel: Class<T>) : DaggerAppCompatActivity() {

    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory

    lateinit var vm: T

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        vm = ViewModelProviders.of(this, viewModelFactory)[viewModel]

    }
}

我该如何解决这个问题?

标签: androidkotlinandroid-livedatacoroutine

解决方案


推荐阅读