0

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).

2
  • 1
    Sorry for the off topic, but is there a reason you're using ArrayList explicitly in your LiveData instead of just List? Because you could simply use map instead of that forEach + add Commented Apr 6, 2021 at 0:07
  • Thanks. I have adopted that. Didn't know how to use the map. Commented Apr 6, 2021 at 1:25

2 Answers 2

2

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.

Sign up to request clarification or add additional context in comments.

11 Comments

wanted to avoid passing the object name in the function name. could we use (..,liveData: MutableLiveData<List<T>>,...)
ClubFSEntity is different for each different call.
This is what i get Type mismatch. Required: List<ClubFSEntity>? Found: List<T!!>
Maybe i can make the whole class generic and declare clubs like this:: private var _mClubs = MutableLiveData<ArrayList<T>>()
Yes of course it should be T in the live data parameter. This was just a typo on my part.
|
0

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()
...
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.