Reputation: 2952
I'm building a very simple game with Jetpack Compose where I have 3 screens:
Each screen has a ViewModel, and a Repository class:
HeroesScreen -> HeroesViewModel -> HeroesRepository
HeroDetailsScreen -> HeroDetailsViewModel -> HeroDetailsRepository
ShoppingCartScreen -> ShoppingCartViewModel -> ShoppingCartRepository
Each repository has between 8-12 different API calls. However, two of them are present in each repo, which is increase/decrease quantity. So I have the same 2 functions in 3 repository and 3 view model classes. Is there any way I can avoid those duplicates?
I know I can add these 2 functions only in one repo, and then inject an instance of that repo in the other view models, but is this a good approach? Since ShoppingCartRepository is not somehow related to HeroDetailsViewModel.
Edit
All 3 view model and repo classes contain 8-12 functions, but I will share only what's common in all classes:
class ShoppingCartViewModel @Inject constructor(
private val repo: ShoppingCartRepository
): ViewModel() {
var incrementQuantityResult by mutableStateOf<Result<Boolean>>(false)
private set
var decrementQuantityResult by mutableStateOf<Result<Boolean>>(false)
private set
fun incrementQuantity(heroId: String) = viewModelScope.launch {
repo.incrementQuantity(heroId).collect { result ->
incrementQuantityResult = result
}
}
fun decrementQuantity(heroId: String) = viewModelScope.launch {
repo.decrementQuantity(heroId).collect { result ->
decrementQuantityResult = result
}
}
}
And here is the repo class:
class ShoppingCartRepositoryImpl(
private val db: FirebaseFirestore,
): ShoppingCartRepository {
val heroIdRef = db.collection("shoppingCart").document(heroId)
override fun incrementQuantity(heroId: String) = flow {
try {
emit(Result.Loading)
heroIdRef.update("quantity", FieldValue.increment(1)).await()
emit(Result.Success(true))
} catch (e: Exception) {
emit(Result.Failure(e))
}
}
override fun decrementQuantity(heroId: String) = flow {
try {
emit(Result.Loading)
heroIdRef.update("quantity", FieldValue.increment(-1)).await()
emit(Result.Success(true))
} catch (e: Exception) {
emit(Result.Failure(e))
}
}
}
All the other view model classes and repo classes contain their own logic, including these common functions.
Upvotes: 1
Views: 540
Reputation: 93591
I don't use Firebase, but going off of your code, I think you could do something like this.
You don't seem to be using the heroId
parameter of your functions so I'm omitting that.
Here's a couple of different strategies for modularizing this:
class IncrementableField(
private val documentReference: DocumentReference,
val fieldName: String
) {
private fun increment(amount: Float) = flow {
try {
emit(Result.Loading)
heroIdRef.update(fieldName, FieldValue.increment(amount)).await()
emit(Result.Success(true))
} catch (e: Exception) {
emit(Result.Failure(e))
}
}
fun increment() = increment(1)
fun decrement() = increment(-1)
}
Then your repo becomes:
class ShoppingCartRepositoryImpl(
private val db: FirebaseFirestore,
): ShoppingCartRepository {
val heroIdRef = db.collection("shoppingCart").document(heroId)
val quantity = IncrementableField(heroIdRef, "quantity")
}
and in your ViewModel, can call quantity.increment()
or quantity.decrement()
.
interface Quantifiable {
val documentReference: DocumentReference
}
fun Quantifiable.incrementQuantity()(amount: Float) = flow {
try {
emit(Result.Loading)
heroIdRef.update("quantity", FieldValue.increment(amount)).await()
emit(Result.Success(true))
} catch (e: Exception) {
emit(Result.Failure(e))
}
}
fun Quantifiable.incrementQuantity() = incrementQuantity(1)
fun Quantifiable.decrementQuantity() = incrementQuantity(-1)
Then your Repository can extend this interface:
interface ShoppingCartRepository: Quantitfiable {
//... your existing definition of the interface
}
class ShoppingCartRepositoryImpl(
private val db: FirebaseFirestore,
): ShoppingCartRepository {
private val heroIdRef = db.collection("shoppingCart").document(heroId)
override val documentReference: DocumentReference get() = heroIdRef
}
Upvotes: 3