My android app has enum class Specialization:
enum class Specialization {
DEVELOPER, MANAGER
}
The values of this class are passed to the viewmodel, which makes a request to the repository based on this:
class EmployeesViewModel @Inject constructor(
private val typeSpecialization: Specialization,
private val getEmployeesListUseCase: GetEmployeesListUseCase
): ViewModel() {
val screenState: Flow<EmployeesFragmentScreenState> = getEmployeesListUseCase(typeSpecialization)
.filter { it.isNotEmpty() }
.map { EmployeesFragmentScreenState.Content(employees = it) as EmployeesFragmentScreenState }
.onStart { emit(EmployeesFragmentScreenState.Loading) }
}
RecycleView:
override fun onBindViewHolder(
holder: SpecializationItemViewHolder,
position: Int
) {
val specItem = getItem(position)
val binding = holder.binding
binding.specializationName.text = specItem.specializationName
binding.idspec.text = specItem.specialty_id.toString()
binding.root.setOnClickListener {
onSpecializationClickListener?.invoke(specItem.specialty_id)
}
}
SpecializationListFragment:
class SpecializationListFragment : Fragment() {
private var _binding: FragmentSpecializationListBinding? = null
private val binding: FragmentSpecializationListBinding
get() = _binding ?: throw RuntimeException("FragmentSpecializationListBinding is null")
private lateinit var specializationListAdapter: SpecializationListAdapter
@Inject
lateinit var viewModelFactory: ViewModelFactory
private val component by lazy{
(requireActivity().application as CoreApplication).component
}
lateinit var viewModel: SpecializationViewModel
override fun onAttach(context: Context) {
component.inject(this)
super.onAttach(context)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentSpecializationListBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this, viewModelFactory)[SpecializationViewModel::class.java]
setupRecyclerView()
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED){
viewModel.screenState.collect{
when(it){
is SpecializationsFragmentScreenState.Content -> {
//Toast.makeText(requireContext(), "Spec Content", Toast.LENGTH_SHORT).show()
binding.progressSpecLayout.visibility = View.GONE
specializationListAdapter.submitList(it.specializations)
}
is SpecializationsFragmentScreenState.Loading -> {
binding.progressSpecLayout.visibility = View.VISIBLE
}
}
}
}
}
}
private fun setupRecyclerView(){
with(binding.rvSpList){
specializationListAdapter = SpecializationListAdapter()
adapter = specializationListAdapter
}
setupClickListener()
}
private fun setupClickListener(){
specializationListAdapter.onSpecializationClickListener = {
launchEmployeesListFragment(it)
}
}
private fun launchEmployeesListFragment(typeSpecialization: Int){
val type = if(typeSpecialization == 101){
Specialization.MANAGER
}else{
Specialization.DEVELOPER
}
findNavController().navigate(SpecializationListFragmentDirections.actionSpecializationListFragmentToEmployeesListFragment(type))
}
}
EmployeesListFragment:
class EmployeesListFragment : Fragment() {
private var _binding: FragmentEmployeesListBinding? = null
private val binding: FragmentEmployeesListBinding
get() = _binding ?: throw RuntimeException("FragmentEmployeesListBinding is null")
private val args by navArgs<EmployeesListFragmentArgs>()
@Inject
lateinit var viewModelFactoryD: ViewModelFactory
private val component by lazy{
(requireActivity().application as CoreApplication).component
}
lateinit var viewModel: EmployeesViewModel
private lateinit var employeeListAdapter: EmployeesListAdapter
override fun onAttach(context: Context) {
component.inject(this)
super.onAttach(context)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentEmployeesListBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this, viewModelFactoryD)[EmployeesViewModel::class.java]
setupRecyclerView()
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED){
viewModel.screenState.collect{
when(it){
is EmployeesFragmentScreenState.Content ->{
binding.progressLayout.visibility = View.GONE
employeeListAdapter.submitList(it.employees)
}
is EmployeesFragmentScreenState.Loading ->{
binding.progressLayout.visibility = View.VISIBLE
}
}
}
}
}
}
private fun setupRecyclerView(){
with(binding.rvEmpList){
employeeListAdapter = EmployeesListAdapter()
adapter = employeeListAdapter
}
setupClickListener()
}
private fun setupClickListener(){
employeeListAdapter.onEmployeeClickListener = {
launchEmployeeFragment(it)
}
}
private fun launchEmployeeFragment(employee: Employee){
findNavController().navigate(EmployeesListFragmentDirections.actionEmployeesListFragmentToEmployeeFragment(employee))
}
}
Next, I just get the specialty type on the next screen and pass it to the ViewModel, which makes a request to the repository via UseCase. Depending on the type, employees of a certain specialty will be received.
How to handle this situation with Dagger 2? When trying to write a method for provides, the error [Dagger/DuplicateBindings] naturally occurs:
@Module
class DomainModule {
@Provides
fun provideSpecializationDeveloper(): Specialization {
return Specialization.DEVELOPER
}
@Provides
fun provideSpecializationManager(): Specialization {
return Specialization.MANAGER
}
}
What you were actually asking is how to pass the navigation argument to the view model of another fragment.
What you want is SavedStateHandle API + Navigation Safe Args.
class EmployeesViewModel @Inject constructor(
private val savedStateHandle: SavedStateHandle,
private val getEmployeesListUseCase: GetEmployeesListUseCase
): ViewModel() {
val typeSpecialization: Specialization = EmployeesListFragmentArgs.fromSavedStateHandle(savedStateHandle)).type
val screenState: Flow<EmployeesFragmentScreenState> = getEmployeesListUseCase(typeSpecialization)
.filter { it.isNotEmpty() }
.map { EmployeesFragmentScreenState.Content(employees = it) as EmployeesFragmentScreenState }
.onStart { emit(EmployeesFragmentScreenState.Loading) }
}
You might have some issues passing the enum direcly - I would just pass the Int and map it in view model to safe the hassle - if you want to go that route - here is how.
val type = if (typeSpecialization == 101) {
Specialization.MANAGER
} else {
Specialization.DEVELOPER
}
But in order to do this you will have to update your ViewModelFactory
to SavedStateViewModelFactory
I know it is a lot of changes to here is something that will work with minimal changes:
class EmployeesViewModel @Inject constructor(
private val getEmployeesListUseCase: GetEmployeesListUseCase
): ViewModel() {
var isInitalized: Boolean = false
private _screenState: MutableStateFlow<EmployeesFragmentScreenState> = MutableStateFlow(EmployeesFragmentScreenState.Loading)
val screenState: StateFlow<EmployeesFragmentScreenState> = _screenState.asStateFlow()
fun initialize(typeSpecialization: Specialization) {
if (!isInitialized) {
isInitialized = true
viewModelScope.launch {
getEmployeesListUseCase(typeSpecialization)
.filter { it.isNotEmpty() }
.map { EmployeesFragmentScreenState.Content(employees = it) as EmployeesFragmentScreenState }
.collect { newState ->
_screenState.update { newState }
}
}
}
}
}
Fragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this, viewModelFactoryD)[EmployeesViewModel::class.java]
viewModel.initialize(arguments?.getInt("type")) //where "type" is the name of the argument in navGraph
setupRecyclerView()
viewModel.screenState.collectAsStateWithLifecycle {
when(it) {
is EmployeesFragmentScreenState.Content ->{
binding.progressLayout.visibility = View.GONE
employeeListAdapter.submitList(it.employees)
}
is EmployeesFragmentScreenState.Loading ->{
binding.progressLayout.visibility = View.VISIBLE
}
}
}
}