Search code examples
androidkotlinmvvmfragmentviewmodel

How to see if two ArrayLists are from the same Type


I'm trying to implement the viewmodel observer with generics, doing this I wont need 7 observers since I'm collecting data from 7 different Fragments.

The goal

Since this 7 fragments store the data inside different ArrayLists types I made a generic ViewModel class to pass that data to my main Activity (fragments holder)

SharedViewModel

class SharedViewModel<T>: ViewModel() {

    var data:MutableLiveData<ArrayList<T>> = MutableLiveData()

    fun setData(anyData:ArrayList<T>){
        data.value = anyData
    }

    val getAnyData:LiveData<ArrayList<T>>
    get() = data

}

Doing this, I just set any array data type inside each fragment

  (activity as MainActivity).getViewModelInstance().setData(xArray)

And in my MainActivity I want to check if that xArray data type is the same type as the one declared globally at MainActivity, if they are equal it should assing the current array data values to the new empty array

MainActivity

private var xArray = arrayListOf<Xclass>()

    onCreate()
    ...

     viewModel = ViewModelProviders.of(this).get(SharedViewModel::class.java)
            viewModel.getAnyData.observe(this, Observer { it:ArrayList<out Any?>
                if(it == xArray){
                    xArray.addAll(it)
                }
            })

The purpose

If I do this, I prevent doing 7 observers in my MainActivity for each fragment and just update one observer with different array types, comparing them and reasigning them will be much less code and easier for me for the architecture.

The problems

There are two problems

  • The first one as mentioned, is just how to compare the current ArrayList got by the observer in my MainActivity to the array that I declared. They should check if both, it and xArray are ArrayLists of the same type
  • Second error is this

    (activity as MainActivity).getViewModelInstance().setData(xArray)

At .setData(xArray) its highlighted red saying Required: Nothing, Found:ArrayList<Xclass>. Thats weird because the setData of SharedViewModel needs Any ArrayList to be passed.

Thanks


Solution

  • After some thought, I came up with a simple, but not fail proof method by utilizing a Class Type combined with the generics for full functionality. I believe this will handle your issues with reusability and removing redundant listeners in the way you were hoping.

    class SharedViewModel <T> (val listType: Class<T>) : ViewModel() {
    
        var data: MutableLiveData<ArrayList<T>> = MutableLiveData()
    
        fun setData(anyData: ArrayList<T>) {
    
            data.value = anyData
        }
    
        inline fun <reified K> isOfInternalType(checkType: Class<K>): Boolean = checkType.typeName == listType.typeName
    }
    

    Set Array Data through viewModel

    class Xclass
    var xArray = arrayListOf<Xclass>()
    
    val viewModel = (activity as MainActivity).getViewModelInstance()
    if (viewModel.isOfInternalType(Xclass::class.java) {
        viewModel.setData(Xclass)
    }
    

    Main Activity

    class Xclass
    private var xArray = arrayListOf<Xclass>()
    
    onCreate()
    ...
    
    viewModel = ViewModelProviders.of(this).get(SharedViewModel::class.java)
    viewModel.getAnyData.observe(this, Observer { data: ArrayList<out Any?> ->
        if (viewModel.isOfInternalType(Xclass::class.java) {
            xArray.addAll(it)
        }
    })
    

    Other Example Uses

    // Examples of use cases outside of initial question
    
    class ViewOne: SharedView<ClassOne>(ClassOne::class.java)
    val testClass = ViewOne()
    
    ViewOne().isOfInternalType(ClassTwo::class.java) // returns false
    ViewOne().isOfInternalType(testClass::class.java) // Error: Cannot use captured type as reified parameter
    
    ViewOne().isOfInternalType(ClassOne::class.java) // returns true
    ViewOne().isOfInternalType(ClassOne().javaClass) // returns true
    testClass.isOfInternalType(ClassOne::class.java) // returns true
    

    What I have done is created an instance of the View's listType expected at instantiation. This needed to be done because classes do not support type reification like an inline function can. This implementation assumes each View will have a static listType... This can be modified to resemble the following:

    class SharedViewModel(listType: Class<*>) : ViewModel() {
        var listType: Class<*> = listType
        private set(value) {
            field = value
        }
    
        fun setData(anyData: ArrayList<*>) {
            data.value = anyData
        }
    
        fun updateListType(newType: Class<*>) {
            listType = newType
        }
    
        inline fun <reified K> isOfInternalType(checkType: Class<K>): Boolean =
            checkType.typeName == listType.typeName
    }
    

    Variable type SharedViewModel Examples

    class ViewOne: SharedView(ClassOne::class.java)
    
    val testClass = ViewOne()
    
    testClass.isOfInternalType(ClassOne::class.java) // returns true
    
    testClass.updateListType(ClassTwo::class.java)
    
    testClass.isOfInternalType(ClassOne::class.java)  // returns false now
    testClass.isOfInternalType(ClassTwo::class.java)  // returns true now
    

    With this implementation of the variable typing, you would want to chain your isOfInternalType with the setData so that you ensure your type safety

    This should have enough functionality for you to be able to accomplish your architecture goals for this project, please let me know if I've misunderstood anything and can help further.