The situation is pretty straightforward. I have a simple android app with 4 fragments displayed through a bottom navigation bar, and a central Room database. Each fragment should be able to perform CRUD operations on the DB through a viewmodel (details are probably irrelevant but I'll show this as well to be sure):
class ViewModel(application: Application): AndroidViewModel(application) {
val readAllIngredients: LiveData<List<Ingredient>>
val readAllRecipes: LiveData<List<Recipe>>
private val ingredientRepository: IngredientRepository
private val recipeRepository: RecipeRepository
init {
val ingredientDAO = ShoppingAppDatabase.getDatabase(application).ingredientDAO()
val recipeDAO = ShoppingAppDatabase.getDatabase(application).recipeDAO()
ingredientRepository = IngredientRepository(ingredientDAO)
recipeRepository = RecipeRepository(recipeDAO)
readAllIngredients = ingredientRepository.allIngredients
readAllRecipes = recipeRepository.allRecipes
}
fun addIngredient(ingredient: Ingredient) {
viewModelScope.launch(Dispatchers.IO) {
ingredientRepository.put(ingredient)
}
}
fun deleteIngredient(ingredient: Ingredient) {
viewModelScope.launch(Dispatchers.IO) {
ingredientRepository.delete(ingredient)
}
}
fun addRecipe(recipe: Recipe) {
viewModelScope.launch(Dispatchers.IO) {
recipeRepository.put(recipe)
}
}
fun updateRecipe(recipe: Recipe) {
viewModelScope.launch(Dispatchers.IO) {
recipeRepository.update(recipe)
}
}
fun updateIngredient(ingredient: Ingredient) {
viewModelScope.launch(Dispatchers.IO) {
ingredientRepository.update(ingredient)
}
}
}
I'm initializing a viewmodel in each fragment, but with limited success. Here's a fragment for which everything works fine:
class InventoryFragment() : Fragment() {
private var listAdapter = IngredientAdapter()
private lateinit var viewModel : ViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_inventory, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
listAdapter = IngredientAdapter()
recycler_view.apply {
layoutManager = LinearLayoutManager(activity)
adapter = listAdapter
}
viewModel = ViewModelProvider(this).get(ViewModel::class.java)
viewModel.readAllIngredients.observe(viewLifecycleOwner, Observer { ingredient -> listAdapter.setData(ingredient) })
add_button.setOnClickListener{
val errorMessages = validateInput()
if(errorMessages.isNotEmpty()) {
displayToast(activity, errorMessages)
}
else {
viewModel.addIngredient(Ingredient(
edit_name.text.toString(),
edit_qty.text.toString().toFloat(),
edit_um.text.toString()
))
listAdapter.notifyDataSetChanged()
displayToast(activity, "Ingredient added")
hideKeyboard(activity, requireView().windowToken)
}
clearInput()
}
}
}
I'm initializing it in the onViewCreated callback and yeah, it works fine. Doing the same thing in a different fragment yields.. different results for some reason.
class BrowseFragment() : Fragment() {
private lateinit var viewModel: ViewModel
var recipeAdapter = RecipeAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_browse, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(ViewModel::class.java)
viewModel.readAllRecipes.observe(viewLifecycleOwner, Observer { recipe -> recipeAdapter.setData(recipe) })
submit_button.setOnClickListener{
var submitFragment = SubmitFragment(recipeAdapter)
var tr = (view.context as FragmentActivity).supportFragmentManager.beginTransaction()
tr.replace(R.id.fragment_container, submitFragment)
tr.commit()
}
browse_recycler_view.apply {
layoutManager = LinearLayoutManager(activity)
adapter = recipeAdapter
}
}
}
When I try to initialize the viewmodel in onViewCreated, I get an IllegalStateException: Can't access ViewModels from detached fragment exception. Creating it in onCreate doesn't work either, since the lifecycle owner is null, which makes sense I guess. What exactly am I doing wrong here?