Search code examples
androidkotlinviewmodelfactory

How do I pass an argument to the view model?


I need to get the parameter from the argument inside my fragment and give it to the view model, so that it filters the list based on it or not. I thought that it is possible to pass this argument to the factory for the view model, and from there to the constructor of the view model itself, so it will have val onlyFavorites and it can be used right away in the init block.

ContentFragment.kt

class ContentFragment : Fragment(), ItemFavoriteClickListener {

    private val viewModel: ContentViewModel by viewModels(factoryProducer = {
        ContentViewModel.Factory()
    })

    private var adapter: MyItemModelsAdapter? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.getBoolean("onlyFavorites", false)
        
        viewModel.items.observe(this) {
            adapter?.items = it
        }
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        recyclerView?.layoutManager = LinearLayoutManager(context)

        adapter = MyItemModelsAdapter(this)
        recyclerView?.adapter = adapter
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_favourites, container, false)
    }

    override fun onFavoriteClick(item: ItemModel, isFavorite: Boolean) {
        viewModel.changeFavoriteState(item, isFavorite)
    }

    companion object {
        fun newInstance(onlyFavorites: Boolean): ContentFragment {
            val contentFragment = ContentFragment()
            val args = Bundle()
            args.putBoolean("onlyFavorites", onlyFavorites)
            contentFragment.arguments = args

            return contentFragment
        }
    }
}

ContentViewModel.kt

class ContentViewModel(
    private val repository: MyItemsRepository
) : ViewModel() {

    class Factory : ViewModelProvider.Factory {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            return ContentViewModel(MyItemsRepositoryImpl.getInstance()) as T
        }
    }

    private val _items: MutableLiveData<List<ItemModel>> = MutableLiveData()
    val items: LiveData<List<ItemModel>>
        get() = _items

    fun changeFavoriteState(item: ItemModel, favorite: Boolean) {
        repository.setFavorite(item, favorite)
    }

    init {
        _items.value = repository.items.filter { it.isFavorite }
        repository.addItemChangeListener {
            _items.value = repository.items.filter { it.isFavorite }
        }
    }
}

I need your help writing the code. I mentally understand how to do it, but I don't know how to write it. I would be very grateful for a written example with an explanation.


Solution

  • It's not that hard as it seems; just create a new public variable inside your ContentViewModel

    Say

    var isFavorite: Boolean = false

    Then instead of init of a ViewModel, put your fetching logic inside a method.

    fun getItems() {
        _items.value = repository.items.filter { it.isFavorite == isFavorite }
    }
    

    And now call this method in fragment's onViewCreated() something like this:

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        recyclerView?.layoutManager = LinearLayoutManager(context)
    
        adapter = MyItemModelsAdapter(this)
        recyclerView?.adapter = adapter
    
        val isFavorite = arguments?.getBoolean("onlyFavorites", false) ?: false
        viewModel.isFavorite = isFavorite
    
        viewModel.getItems()
        
        viewModel.items.observe(this) {
            adapter?.items = it
        }
    }
    

    This way, you have the value for isFavorite before you actually call the getItems method.

    I haven't tested this code, but I'm pretty sure it'll work.

    Leave a comment if you need any more help with this.