My app has two fragments: HomeFragment
and StatusFragment
. HomeFragment
binds some connection info to the UI. Info is obtained from broadcast receiver. At the beginning, data binding works in HomeFragment
but when user go to the StatusFragment
and then back to the HomeFragment
, info is not bind.
HomeFragment, StatusFragment and HomeFragmentViewModel classes are shown below:
class HomeFragment : Fragment() {
private var binding: FragmentHomeBinding? = null
private var connectionStatus: String? = "Offline"
private var connectionType: String? = ""
private lateinit var mReceiver: ConnectivityChangeBroadcastReceiver
lateinit var homeFragmentViewModel: HomeFragmentViewModel
lateinit var repo: Repository
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentHomeBinding.inflate(inflater, container, false)
binding?.lifecycleOwner = viewLifecycleOwner
repo = Repository().getInstance()!!
val viewModelFactory = HomeFragmentViewModelFactory(repo)
homeFragmentViewModel =
ViewModelProvider(this, viewModelFactory)[HomeFragmentViewModel::class.java]
binding?.homeFragmentViewModel = homeFragmentViewModel
this.mReceiver = ConnectivityChangeBroadcastReceiver()
mReceiver.mData.observe(this, Observer {
repo.setInfo(mReceiver.getData())
homeFragmentViewModel.getConnectionInfo()
})
homeFragmentViewModel.navToStatusFragment.observe(this, Observer {
if (it==true){
this.findNavController().navigate(R.id.action_homeFragment_to_statusFragment)
homeFragmentViewModel.doneNavigateToStatusFragment()
}
})
return binding?.root
}
override fun onStart() {
super.onStart()
requireActivity().registerReceiver(
mReceiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
)
repo.setInfo(mReceiver.getData())
}
override fun onStop() {
super.onStop()
requireActivity().unregisterReceiver(mReceiver)
}
}
class StatusFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
requireActivity().onBackPressedDispatcher.addCallback(this){
requireActivity().supportFragmentManager.popBackStack()
}
return inflater.inflate(R.layout.fragment_status, container, false)
}
}
class HomeFragmentViewModel(private val repo: Repository?) : ViewModel() {
private val viewModelJob = Job()
private val _navToStatusFragment = MutableLiveData<Boolean?>()
val navToStatusFragment: LiveData<Boolean?>
get() = _navToStatusFragment
private val _connected = MutableLiveData<String?>()
val connected: LiveData<String?>
get() = _connected
private val _connectionType = MutableLiveData<String?>()
val connectionType: LiveData<String?>
get() = _connectionType
init {
_navToStatusFragment.value = false
}
var isConnected: LiveData<Boolean> = connected.map {
when (it) {
"Online" -> true
else -> false
}
}
fun doneNavigateToStatusFragment(){
_navToStatusFragment.value = false
}
fun navigateToStatusFragment(){
_navToStatusFragment.value = true
}
fun getConnectionInfo() {
when (repo?.getInfo()?.value) {
0 -> {
_connected.value = "Offline"
_connectionType.value = "Check Your Connection"
_connectionDetails.value = null
}
1 -> {
_connected.value = "Online"
_connectionType.value = "Cellular"
_connectionDetails.value = null
}
2 -> {
_connected.value = "Online"
_connectionType.value = "WIFI"
_connectionDetails.value = null
}
3 -> {
_connected.value = "Online"
_connectionType.value = "VPN"
_connectionDetails.value = null
}
}
Log.d("LOGGED", "Info Checked ...")
}
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
}
I did:
regiterReceiver()
in onResume()
, tooviewLifeCycleOwner
instead of this
for binding.lifeCycleOwner
popBackStack()
in StatusFragment
when back button pressedUPDATE: The problem is that after going back to HomeFragment, onCreateView() is invoked, but HomeFragmentViewModelFactory() is not invoked! -> I need it for sending new repo object to view model
About your update note: viewModel will not be recreated upon coming back to a fragment so the factory is not called.
You need to know about viewModel Owner. in order to create a viewModel we need a ViewModelStoreOwner
which is an interface which we will see in a bit, and activity
, fragment
, navgraph
and navBackStackEntry
classes implement this interface. This is the first argument of ViewModelProvider
which in your case is this which means the current fragment.
What does this ViewModelStoreOwner
do? basically it has a map which it keeps the instances of the viewModel created, when asking for a viewModel using ViewModelProvider
first it looks inside this map to see if it has created it or not, if it is created it returns the previously created viewModel otherwise it will call the ViewModelFactory
to create a new one, after that it stores it in this map for later use.
What does that mean? for a fragment this map is create in onCreate callback and destroyed in onDestroy callback (not onDestroyView) so when you navigate to a new fragment, since this fragment is not yet destroyed, The viewModel still exists in this map, therefore upon coming back the one from the map which is previously created is returned.
PossibleSolution: Create a function in viewModel and pass the repo to it (instead of constructor arguments), or do not use viewModel at all!