nyxee
nyxee

Reputation: 2811

Modularising to create a function with variable Types in Kotlin

I have a funtion like this in many places in my project

class ClubsViewModel():  BaseViewModel() {
    private var _mClubs = MutableLiveData<ArrayList<ClubFSEntity>>()
    ...

    private fun listenToFireStoreClubs() {
        mFirestore.collection("Clubs").addSnapshotListener { snapshot, e ->
            // if there is an exception we want to skip.
            if (e != null) {
                return@addSnapshotListener
            }
            // if we are here, we did not encounter an exception
            if (snapshot != null) {
                val allClubs = ArrayList<ClubFSEntity>()
                snapshot.documents.forEach {
                    it.toObject(ClubFSEntity::class.java)?.let {
        
                        allClubs.add(it)
                    }
                }
                _mClubs.value = allClubs
            }
        }
    }
}
...
...
class ProjectsViewModel():  BaseViewModel() {
    private var _mProjects = MutableLiveData<ArrayList<ProjectsFSEntity>>()

    private fun listenToFireStoreProjects() {
        //Code Here is Similar to the Code in the similar function in ClubsViewModel.
    }
}
...
...
open class BaseViewModel() : ViewModel()  {
    protected val mFirestore = Firebase.firestore
    protected var mStorageReferenence: StorageReference

    protected val _networkState = MutableLiveData<NetworkState>()
    protected val networkState: LiveData<NetworkState> = _networkState

    //I would like to make this function private to access the above variables.
    inline fun listenToFireStoreCollection[TO MOVE HERE SO THAT ALL MY OTHER VIEW MODELS CAN CALL IT]
}
...
...
@Parcelize
data class ClubFSEntity(val title: String="",
                        val description: String="",
                        val startDate: String?=null,
                        var clubId : String=""): Parcelable

It's easy to modularize out the "Clubs" parameter. The Firebase data from that document list goes to an arraylist of ClubFSEntity data objects. When adding the Firebase snapshot data to the allClubs array, we convert using ClubFSEntity::class.java.

I have many places with this same code, but only three things change ("Clubs", ClubFSEntity and ClubFSEntity::class.java). I would like to create a function with those three items as a variables.

I don't even know what to search for. I have tried to use generics but can't get it so far.

I would like to also use a generic class if possible.

I would prefer to initialize the ViewModel like so:

class ClubsFragment : Fragment() {
  private val mClubsViewModel: ClubsViewModel<ClubsFSEntity> by viewModels()

}

And, to change the ViewModel definition to something like:

class ClubsViewModel<T> () :  BaseViewModel() {

    private var _mClubs = MutableLiveData<List<T>>()
...
}

Each view model would call listenToFirestoreCollection with its own parameter(s).

Upvotes: 0

Views: 1006

Answers (2)

nyxee
nyxee

Reputation: 2811

I finished like this:

class ClubsViewModel<T> (clazz: Class<T>) :  BaseViewModel<T>(clazz) {      //<<  THIS IS WHERE THE CURRENT PROBLEM WAS.

    private var _mClubs = MutableLiveData<List<T>>()
     listenToFireStoreCollection("Clubs", _mClubs)

...
}

class BViewModel<T> (clazz: Class<T>) :  BaseViewModel<T>(clazz) {          //<<   THIS IS WHERE THE CURRENT PROBLEM WAS.

    private var _mBs = MutableLiveData<List<T>>()
    listenToFireStoreCollection("Bname", _mBs)
...
}

class BaseViewModel<T>(val clazz: Class<T>) {
    protected val mFirestore = Firebase.firestore

    protected  fun listenToFireStoreCollection(val collectionName: String, liveData: MutableLiveData<List<T>>) 
        mFirestore.collection(collectionName).addSnapshotListener { snapshot, e ->
            // if there is an exception we want to skip.
            if (e != null) {
                return@addSnapshotListener
            }
            // if we are here, we did not encounter an exception
            if (snapshot != null) {
                liveData.value = snapshot.documents.mapNotNull { it.toObject(clazz) }
            }
        }
    }
}
//FRAGMENT EXAMPLES.
class ClubsFragment : Fragment() {

    private val mClubsViewModel: ClubsViewModel<ClubsFSEntity> by viewModels()
...
}
class BsFragment : Fragment() {

    private val mBsViewModel: BsViewModel<BsFSEntity> by viewModels()
...
}

Upvotes: 0

Joffrey
Joffrey

Reputation: 37660

Let's first simplify your initial function:

private var _mClubs = MutableLiveData<List<ClubFSEntity>>()

private fun listenToFireStoreClubs() {
    mFirestore.collection("Clubs").addSnapshotListener { snapshot, e ->
        // if there is an exception we want to skip.
        if (e != null) {
            return@addSnapshotListener
        }
        // if we are here, we did not encounter an exception
        if (snapshot != null) {
            _mClubs.value = snapshot.documents.mapNotNull { 
                it.toObject(ClubFSEntity::class.java)
            }
        }
    }
}

You could make it more generic like this:

private fun <T> listenToFireStoreCollection(
    collectionName: String,
    liveData: MutableLiveData<List<T>>,
    clazz: Class<T>,
) {
    mFirestore.collection(collectionName).addSnapshotListener { snapshot, e ->
        // if there is an exception we want to skip.
        if (e != null) {
            return@addSnapshotListener
        }
        // if we are here, we did not encounter an exception
        if (snapshot != null) {
            liveData.value = snapshot.documents.mapNotNull { it.toObject(clazz) }
        }
    }
}

You can also go one step further and additionally define a reified version of this, so you don't have to pass in the Class<T> explicitly each time:

private inline fun <reified T> listenToFireStoreCollection(
    collectionName: String,
    liveData: MutableLiveData<List<T>>,
) = listenToFireStoreCollection(collectionName, liveData, T::class.java)

If the live data and the function are in the same class, you can also make the class generic itself like the following:

class FireStoreLiveData<T>(
    val collectionName: String,
    val clazz: Class<T>,
) {
    private var _mClubs = MutableLiveData<List<ClubFSEntity>>()

    private fun listenToFireStoreCollection() {
        mFirestore.collection(collectionName).addSnapshotListener { snapshot, e ->
            // if there is an exception we want to skip.
            if (e != null) {
                return@addSnapshotListener
            }
            // if we are here, we did not encounter an exception
            if (snapshot != null) {
                liveData.value = snapshot.documents.mapNotNull { it.toObject(clazz) }
            }
        }
    }
}

But it's hard to tell how to design the API of this class without more info about how you intend to use it from outside (everything is private right now, so not much use). For instance, who starts the listener? Is there a way to stop it? etc.

Upvotes: 2

Related Questions