androidkotlinandroid-viewmodelmaster-detail

Shared View Model in Android Not Seen by Child Fragment


In my Android project, I have a Primary/Detail view consisting of an activity and multiple fragments that contain the list and detail views. For this setup, I would like to use a shared view model in order to reduce the needed activity-to-fragment and fragment-to-fragment communication. I have not been able to access the view Model that I created in my Activity from its child fragments. Trying to access the ViewModel from a fragment, I get a java.lang.RuntimeException: Cannot create an instance of class MyViewModel.

To simplify matters, I have created a new Android project and added an activity using

New -> Activity -> Primary/Details Views Flow

That creates a basic functional app containing the essentials I'm looking for.

As my ViewModel takes dependencies, I am using the guidance provided at Create ViewModels with dependencies. I am creating the view Model using private val viewModel: MyViewModel by viewModels { MyViewModel.Factory }. Within my fragments, I declare the view model as private val viewModel: MyViewModel by activityViewModels(). My view model is as follows:

public class MyViewModel(
    private val application: SharedViewModelApplication
) : ViewModel() {

    fun viewModelTest(): String {
        return "ViewModel is ready!"
    }

    private var dbManager: DatabaseManager = DatabaseManager(application)

    override fun onCleared() {
        super.onCleared()
        dbManager.close()
    }

    companion object {
        val Factory: ViewModelProvider.Factory = viewModelFactory {
            initializer {
                val application = this[APPLICATION_KEY] as SharedViewModelApplication
                MyViewModel(
                    application = application
                )
            }
        }
    }
}

This includes a small test method that I am calling from my ItemDetailFragment in order to see if the ViewModel is available. This is where the crash happens.

class ItemListFragment : Fragment() {

    private val viewModel: MyViewModel by activityViewModels()

    private var _binding: FragmentItemListBinding? = null
    private val binding get() = _binding!!

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        _binding = FragmentItemListBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val recyclerView: RecyclerView = binding.itemList
        val itemDetailFragmentContainer: View? = view.findViewById(R.id.item_detail_nav_container)
        setupRecyclerView(recyclerView, itemDetailFragmentContainer)

        // Check ViewModel => app crashes
        Log.v(TAG, viewModel.viewModelTest())
    }

// (...)

}

This is my code for the activity (view model declaration added to the generated code):

class ItemDetailHostActivity : AppCompatActivity() {

    private val viewModel: MyViewModel by viewModels { MyViewModel.Factory }
    private lateinit var appBarConfiguration: AppBarConfiguration

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding = ActivityItemDetailBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment_item_detail) as NavHostFragment
        val navController = navHostFragment.navController
        appBarConfiguration = AppBarConfiguration(navController.graph)
        setupActionBarWithNavController(navController, appBarConfiguration)
    }

    override fun onSupportNavigateUp(): Boolean {
        val navController = findNavController(R.id.nav_host_fragment_item_detail)
        return navController.navigateUp(appBarConfiguration)
                || super.onSupportNavigateUp()
    }
}

There were a number of suggestions for a similar question, and I tried them as far as they could be applied to my situation, but could not resolve the issue.


Solution

  • When declaring the view model in the fragments, if view model factory is used, the factory must also be provided for retrieving the view model associated with the hosting activity.

    Replace

    private val viewModel: MyViewModel by activityViewModels()
    

    with

    private val viewModel: MyViewModel by activityViewModels { MyViewModel.Factory }