Zdaje sobie sprawę, że troche nie jasno opisałem jakie jest dokładnie pytanie.
Najbardziej drażni mnie w tym projekcie że odpytuje o usera za każdym razem we viewModelu gdy ten jest tworzony więc praktycznie na każdym widoku co uważam za bezsensowne w dłuższej perspektywie. Zastanawiam się nad optymalizacją w jaki sposób mieć jedną instancję usera i aktualizować ją gdy zajdzie potrzeba ale każdy viewModel dostawał by istniejącą już instancje, w domyśle tą samą. Nie wiem czy jasno to opisuje, brak mi jeszcze szerszego kontekstu chyba żeby to rzeczowo wyjaśnić.
Na ten moment po przejściu do przykładowego fragmentu, tworzony jest też viewModel, która na wstępie pyta repozytorium(nie wazne czy remote czy local) o dane użytkownika a wolałbym żeby dostawał już gotowca, każdy viewModel operowałby na tym samym.
Repo
class UserRepositoryImpl
@Inject
constructor(
private val databaseInteractor: DatabaseInteractor,
private val authenticator: Authenticator,
private val dispatcherProvider: DispatcherProvider,
private val inputDatabaseUserMapper: NullableInputDatabaseUserMapper,
private val outputDatabaseUserMapper: NullableOutputDatabaseUserMapper
) : UserRepository {
private val currentUserUid = authenticator.getCurrentUserUid()
override suspend fun createAccount(
email: String,
password: String,
firstName: String,
lastName: String
): Result<Nothing> = withContext(dispatcherProvider.io) {
try {
authenticator
.createUserWithEmailAndPassword(email, password)
.await()
val currentUid = authenticator.getCurrentUserUid()
currentUid?.let { uid ->
val user = User(
uid,
firstName,
lastName,
email,
listOf(),
emptyMap()
)
val databaseUser = outputDatabaseUserMapper.mapToEntity(user)
databaseInteractor
.setDocumentInCollection(
COLLECTION_USERS,
uid,
databaseUser
)
.await()
Result.Success(null)
} ?: Result.Error.AuthenticationError(null)
} catch (exc: Exception) {
Result.Error.NetworkError(exc.message)
}
}
override suspend fun loginUser(email: String, password: String): Result<Nothing> =
withContext(dispatcherProvider.io) {
try {
authenticator
.loginUserWithEmailAndPassword(
email,
password
)
.await()
Result.Success(null)
} catch (exc: Exception) {
Result.Error.NetworkError(exc.message)
}
}
override suspend fun getUserData(): Result<User> =
withContext(dispatcherProvider.io) {
currentUserUid?.let { uid ->
try {
val documentSnapshot = databaseInteractor
.getSingleDocument(COLLECTION_USERS, uid)
.await()
val response = documentSnapshot.toObject(DatabaseUser::class.java)
val result = inputDatabaseUserMapper.mapFromDatabase(response)
Result.Success(result)
} catch (exc: Exception) {
Result.Error.NetworkError(exc.message)
}
} ?: Result.Error.AuthenticationError(null)
}
override suspend fun addToCart(productUid: String): Result<Nothing> =
withContext(dispatcherProvider.io) {
currentUserUid?.let { uid ->
try {
databaseInteractor
.addToFieldInDocument(
COLLECTION_USERS,
uid,
productUid
)
.await()
Result.Success(null)
} catch (exc: Exception) {
Result.Error.NetworkError(exc.message)
}
} ?: Result.Error.AuthenticationError(null)
}
override suspend fun removeFromCart(productUid: String): Result<Nothing> =
withContext(dispatcherProvider.io) {
currentUserUid?.let { uid ->
try {
databaseInteractor
.removeFromFieldInDocument(
COLLECTION_USERS,
uid,
productUid
)
.await()
Result.Success(null)
} catch (exc: Exception) {
Result.Error.NetworkError(exc.message)
}
} ?: Result.Error.AuthenticationError(null)
}
override suspend fun clearUserCart(): Result<Nothing> =
withContext(dispatcherProvider.io) {
currentUserUid?.let { uid ->
try {
databaseInteractor
.deleteFieldInDocument(
COLLECTION_USERS,
uid
)
.await()
Result.Success(null)
} catch (exc: Exception) {
Result.Error.NetworkError(exc.message)
}
} ?: Result.Error.AuthenticationError(null)
}
override suspend fun updateAddress(userAddress: Map<String, String>): Result<Nothing> =
withContext(dispatcherProvider.io) {
currentUserUid?.let { uid ->
try {
databaseInteractor
.updateDocument(
COLLECTION_USERS,
uid,
userAddress
)
.await()
Result.Success(null)
} catch (exc: Exception) {
Result.Error.NetworkError(exc.message)
}
} ?: Result.Error.AuthenticationError(null)
}
}
viewModel
@HiltViewModel
class ProductDetailFragmentViewModel
@Inject
constructor(
savedStateHandle: SavedStateHandle,
private val productRepository: ProductRepository,
private val userRepository: UserRepository
) : ViewModel() {
private val productUid = savedStateHandle.get<String>("productUid")
private val _currentProduct = MutableLiveData<Result<Product>>()
val currentProduct: LiveData<Result<Product>>
get() = _currentProduct
private val _currentUser = MutableLiveData<Result<User>>()
val currentUser: LiveData<Result<User>>
get() = _currentUser
private val _navigate = MutableLiveData<Event<Boolean>>()
val navigate: LiveData<Event<Boolean>>
get() = _navigate
val isNotInCart = Transformations.map(_currentUser) {
it.data?.cart?.let { userCart -> productUid !in userCart } ?: true
}
private fun getSingleProduct() {
productUid?.let { uid ->
viewModelScope.launch {
val result = productRepository.getSingleProduct(uid)
_currentProduct.postValue(result)
}
}
}
private fun getCurrentUser() {
viewModelScope.launch {
val result = userRepository.getUserData()
_currentUser.postValue(result)
}
}
private fun addToCart() {
productUid?.let { uid ->
viewModelScope.launch {
userRepository.addToCart(uid)
}
}
}
fun handleAddToCartClick() {
currentUser.value?.data?.let {
addToCart()
getCurrentUser()
} ?: navigate()
}
private fun navigate() {
_navigate.value = Event(true)
}
init {
getSingleProduct()
getCurrentUser()
}
}
Współdzielone ViewModele mają trochę inne zastosowanie. Ważny też jest tzw. Scope w jakim istnieje współdzielony ViewModel.
Np. jak robisz jakiś flow (zamówienie i płatność) który zawiera kilka stepów to dobrze jest kiedy każdy ekran ma swój VM a stan poszczególnych stepów zapisujesz w SVM.
To jest świetny przykład użycia, dziękuje!