How to initialize an interface for viewmodel in a @Preview @Composable

68 Views Asked by At

my problem lies with how to initialize an interface in a viewmodel that is to be used in a @Preview @Composable function. My preview is not showing up in Android Studio because of the following stack trace nor is my next activity (Composables) when I run in emulator or device.

https://1drv.ms/w/s!AnsFOFM6YEPJqKYPTlOnwwfwZ1NNAQ?e=QieFPV

The following codes shows the problem area:

@Composable
fun WishListViewModelData(fbVM: FirebaseWishListViewModel) {
val fb = Firebase
val loggedInCurrentUserEmail = fb.auth.currentUser?.email.toString()
val loggedInEmail: String = loggedInCurrentUserEmail
val recipState: State<RecipientData> = fbVM.uiRecipientState.collectAsState()
val purchaserState: State<PurchaserData> = fbVM.uiPurchaserState.collectAsState()
RecipientWishList(
    loggedInEmail = loggedInEmail,
    recipState = recipState,
    purchaserState = purchaserState,
    fbVM = fbVM
)

}

@Preview(showBackground = true, apiLevel = 33)
@Composable
fun WishListBody() {
lateinit var contactsRepository: ContactsRepository
val repoContacts = contactsRepository
val viewModel = FirebaseWishListViewModel(repoContacts)
WishListViewModelData(fbVM = viewModel)
}

@SuppressLint("SuspiciousIndentation")
@OptIn(ExperimentalGlideComposeApi::class)
@Composable
fun RecipientWishList(
loggedInEmail: String?,
recipState: State<RecipientData>,
purchaserState: State<PurchaserData>,
fbVM: FirebaseWishListViewModel
) {

val requestManager = Glide.with(LocalContext.current)
val imageFileName: String? = null
val showDialog = remember {mutableStateOf(false)}

var selectedRecipientProfile: MutableMap<String,Any> = mutableMapOf()
val purchaserInfo = purchaserState.value
val purchaserImages = purchaserState.value.purchaserImage?.value!!

Box(
    Modifier
        .wrapContentHeight(Alignment.CenterVertically)
        .wrapContentWidth(Alignment.CenterHorizontally)
)
{
   Column (
        modifier = Modifier
            .fillMaxWidth(1f)
            .fillMaxHeight(1f)

    )
    {
       // Purchaser Icon Button
        Button(
            onClick = { showDialog.value = true },
            modifier = Modifier
                .wrapContentWidth(Alignment.CenterHorizontally)
                .wrapContentHeight(Alignment.CenterVertically),
        ) {
            if (showDialog.value) {

                ShowPopUp(purchaserImages = purchaserImages, fbVM = fbVM)
            }
        }
        // Active recipient picture with gift items
    Row (modifier = Modifier.wrapContentWidth(Alignment.CenterHorizontally)){
        val recipList = fbVM.getAllRecipientsInfo(loggedInEmail)

        SetRecipientImage(recipientMap = recipList, rm = requestManager, imageURL = 
        recipState.value.recipientImage.toString() )
        val productMap: MutableMap<String, String> = mutableMapOf()
        GetListItems(products = productMap)
    }
        //List of all recipients that belongs to signed in user

    LazyRow(state = rememberLazyListState()){

        val numRecipients = fbVM.getAllRecipientsInfo(loggedInEmail.toString()).count()
        items(numRecipients) {
            ElevatedButton(onClick = {
                selectedRecipientProfile =
                    loggedInEmail?.let { it1 -> switchRecipientProfile(it1, 
   fbVM.getAllRecipientsInfo(loggedInEmail)) }!!
            }) {
                GlideImage(model = recipState.value.recipientImage!!, 
     contentDescription = "${recipState.value.recipientFirstName}"
                    .plus("")
                    .plus("${recipState.value.recipientLastName}")
                )
                Text (recipState.value.recipientFirstName.toString())
                Text (recipState.value.recipientLastName.toString())
                Text (recipState.value.recipientAddress.toString())
                Text (recipState.value.recipientCity.toString())
                Text (recipState.value.recipientState.toString())
                Text (recipState.value.recipientZipCode.toString())
                Text (recipState.value.recipientPhone.toString())
                Text (recipState.value.recipientEmail.toString())

            }

        }

    }


   }

}
}

My view model looks like the following:


package com.tritongames.shoppingwishlist.data.viewmodels

import android.util.Log
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.google.firebase.Firebase
import com.google.firebase.auth.auth
import com.google.firebase.firestore.DocumentSnapshot
import com.google.firebase.firestore.firestore
import com.google.firebase.storage.FirebaseStorage
import com.tritongames.shoppingwishlist.data.repository.contacts.ContactsRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.getAndUpdate
import kotlinx.coroutines.launch
import java.io.File
import javax.inject.Inject

data class PurchaserData(
val purchaserImage: MutableState<MutableMap<String, String>>? = null,
val purchaserFirstName: MutableState<String?>? = null,
val purchaserLastName: MutableState<String?>? = null,
val purchaserAddress: MutableState<String?>? = null,
val purchaserCity: MutableState<String?>? = null,
val purchaserState: MutableState<String?>? = null,
val purchaserZipCode: MutableState<String?>? = null,
val purchaserEmail: MutableState<String?>? = null,
val purchaserPhone: MutableState<String?>? = null,
val purchaserPassword: MutableState<String?>? = null,
val purchaserUserName: MutableState<String?>? = null,
)
data class RecipientData(
val recipientImage: MutableState<String?>? = null,
val recipientFirstName: MutableState<String?>? = null,
val recipientLastName: MutableState<String?>? = null,
val recipientAddress: MutableState<String?>? = null,
val recipientCity: MutableState<String?>? = null,
val recipientState: MutableState<String?>? = null,
val recipientZipCode: MutableState<String?>? = null,
val recipientEmail: MutableState<String?>? = null,
val recipientPhone: MutableState<String?>? = null,
val recipientPassword: MutableState<String?>? = null,
val recipientUserName: MutableState<String?>? = null,
 )

  @HiltViewModel
  class FirebaseWishListViewModel @Inject 

constructor(private val contactsRepository: ContactsRepository) : ViewModel() {

  fun repoContacts(): ContactsRepository 
   {
   return contactsRepository
  }

  private val _uiPurchaserState = MutableStateFlow(PurchaserData())
  val uiPurchaserState: StateFlow<PurchaserData> = _uiPurchaserState.asStateFlow()

  private val _uiRecipientState = MutableStateFlow(RecipientData())
  val uiRecipientState: StateFlow<RecipientData> = _uiRecipientState.asStateFlow()

  companion object {
    @Suppress("UNCHECKED_CAST")
    class FirebaseWishListViewModelFactory(private val contactsRepository: 
  ContactsRepository):
        ViewModelProvider.Factory {

        override fun <T : ViewModel> create(modelClass: Class<T>): T =
            with(modelClass){

                lateinit var vm: T
                when{
                    isAssignableFrom(FirebaseWishListViewModel::class.java) -> 
 FirebaseWishListViewModel(contactsRepository)

                    else -> { throw IllegalArgumentException("Unknown ViewModel class: 
 ${modelClass.name}")}
                }


            } as T

    }
}



fun getAllPurchaserPictures(loggedInEmail: String): MutableMap<String,String> {
    val purchaserImagesMap: MutableMap<String,String> = mutableMapOf()
    viewModelScope.launch(Dispatchers.IO) {
        val purchPictures = _uiPurchaserState.getAndUpdate {
            val pics = 

mutableStateOf(contactsRepository.getPurchaserPictures(loggedInEmail)) PurchaserData(pics) } purchPictures.purchaserImage?.value?.let { purchaserImagesMap.putAll(it) }

    }
    return purchaserImagesMap
}

// ContactsRepositoryImpl.kt

  override suspend fun 
   getPurchaserPictures(loggedInEmail: 
  String): 
   MutableMap<String, String> {

    val filePath = StringBuilder()
    filePath.append("purchasers/")
        .append(loggedInEmail)
        .append("/Images")
    val storage: FirebaseStorage = 
FirebaseStorage.getInstance()
    var imageFileNames: 
MutableMap<String,String>

   storage.reference.child("purchasers/$loggedIn 
  Email/Images" )
        .listAll()
        .addOnSuccessListener { it ->
            for(item: StorageReference in 
  it.items) {
                val name = item.name
                val path = item.path
                imageFileNames = 
 mutableMapOf(name to path)
                
 Log.d("ContactsRepositoryImpl", 
 imageFileNames.values.toString())
            }


        }


    return imageFileNames
}
1

There are 1 best solutions below

8
Paulo C. On

Like documentation says android preview has some limitations.

"Previews are limited when using ViewModel within a composable. The previews system is not capable of constructing all of the parameters passed to a ViewModel, such as repositories, use cases, managers, or similar."

But, you can solve this easy, just make your component decoupled of the ViewModel object.(The documentation show other examples)

Example:

@Composable
fun ProfileScreen(viewModel: ProfileViewModel = viewModel()) {
    ProfileScreenContent(
        name = viewModel.name,
        onNameChanged = viewModel::onNameChanged
    )
}

@Composable
fun ProfileScreenContent(name: String, onNameChanged: (name: String) -> Unit) {
    //TODO ...view...
}

@Preview
@Composable
fun ProfileScreenContentPreview() { // And here you can mock whatever you want
    ProfileScreenContent(
        name = "Test",
        onNameChanged = {})
}