Home > OS >  My response data return " kotlinx.coroutines.flow.SafeFlow@1493a74"
My response data return " kotlinx.coroutines.flow.SafeFlow@1493a74"

Time:10-31

I am trying to do dictionary app using kotlin language. I built the project with mvvm and clean architecture. I have been trying to pull vocabulary information from the internet using jsoap. I am using flow for data. I couldnt find where the issiue is. Normally, the words should appear on the screen or I should be able to see the data when I println on the console.But I can't see it on the screen or on the console, probably because the data coming from the internet is as follows.

kotlinx.coroutines.flow.SafeFlow@1493a74

I am sharing my codes below

ExtractedData

data class ExtractedData(
    var id :Int = 0,
    var word:String = "",
    var meaning :String = ""
)

I created ExtractedData class to represent vocabulary or word data from internet

WordInfoRepositoryImpl

class WordInfoRepositoryImpl @Inject constructor(
    private val api:DictionaryApi
) : WordInfoRepository {

  
    //get words with meanings on the internet using jsoap

    override fun getEventsList(): Flow<Resource<MutableList<ExtractedData>>> = flow {



            emit(Resource.Loading())

            val listData = mutableListOf<ExtractedData>()


            try {
                val url = "https://ielts.com.au/australia/prepare/article-100-new-english-words-and-phrases-updated-2020"


                val doc = withContext(Dispatchers.IO){
                    Jsoup.connect(url).get()//-->Here it gives the following warning even though I have it in withContext `Inappropriate blocking method call` 
                }

                val table = doc.select("table")
                val rows = table.select("tr")
                val eventsSize = rows.size


                for (i in 1 until eventsSize) {
                    val row = rows[i]
                    val cols = row.select("td")
                    val word = cols[0].text()
                    val meaning = cols[1].text()

                    listData.add(ExtractedData(i,word,meaning))

                }

            }
            catch (e: IOException) {
                emit(Resource.Error("IO Exception"))
            }
            catch (e : HttpException) {
                emit(Resource.Error("HTTP EXCEPTION"))
            }


            emit(Resource.Success(listData))


        }

}

getEventsList is in my WordInfoRepositoryImpl class in my data layer here I am pulling data from internet using jsoap

WordInfoRepository

interface WordInfoRepository {

     fun getEventsList(): Flow<Resource<MutableList<ExtractedData>>>

}

this is the interface that I reference wordInforepositoryImpl in the data layer in my interface domain layer

GetWordsAndMeaningsOnTheInternetUseCase

class GetWordsAndMeaningsOnTheInternetUseCase@Inject constructor(
    private val repository: WordInfoRepository
){


     operator fun invoke() : Flow<Resource<MutableList<ExtractedData>>> {

        return repository.getEventsList()


    }
    
}

GetWordsAndMeaningsOnTheInternetUseCase is my usecase in my domain layer

ViewModel

@HiltViewModel
class MostUsedWordScreenViewModel @Inject constructor(
    private val getWordsAndMeaningsOnTheInternetUseCase: GetWordsAndMeaningsOnTheInternetUseCase
) : ViewModel() {


    private var searchJob: Job? = null

    private val _state = mutableStateOf(MostUsedWordState())
    val state: State<MostUsedWordState> = _state

    init {

        fetchData()
        
    }

    private fun fetchData()  {

        searchJob?.cancel()

        searchJob = viewModelScope.launch(IO) {

            getWordsAndMeaningsOnTheInternetUseCase().onEach { result ->

                when (result) {

                    is Resource.Success -> {
                        _state.value = state.value.copy(
                            mostWordUsedItems = result.data ?: mutableListOf(),
                            isLoading = false

                        )
                    }

                    is Resource.Error -> {
                        _state.value = state.value.copy(
                            mostWordUsedItems = result.data ?: mutableListOf(),
                            isLoading = false
                        )
                    }

                    is Resource.Loading -> {
                        _state.value = state.value.copy(
                            mostWordUsedItems = result.data ?: mutableListOf(),
                            isLoading = true
                        )
                    }
                }


            }

        }


    }


}

MostUsedWordScreen

@Composable
fun MostUsedWordScreen(viewModel: MostUsedWordScreenViewModel = hiltViewModel()) {

    val state = viewModel.state.value
    println("state --- >>> " state.mostWordUsedItems)
    LazyColumn(
        modifier = Modifier.fillMaxSize()
    ) {
        items(state.mostWordUsedItems.size) { i ->
            val wordInfo = state.mostWordUsedItems[i]
            if(i > 0) {
                Spacer(modifier = Modifier.height(8.dp))
            }
            MostUsedWordItem(word = wordInfo)
            if(i < state.mostWordUsedItems.size - 1) {
                Divider()
            }
        }
    }


}

@Composable
fun MostUsedWordItem(word : ExtractedData ) {

   // println("this is MostUsedWordItem")


    Column(modifier = Modifier
        .padding(5.dp)
        .fillMaxWidth()) {


     Text(text = word.word,
     modifier = Modifier.padding(3.dp),
     textAlign = TextAlign.Center,
     fontSize = 18.sp,
     )


    }


}

It is included in the MostUsedWordScreenViewModel and MostUsedWordScreen presententation layer

Where I println("state --- >>> " state.mostWordUsedItems) in MostUsedWordScreen, the state console shows as empty like this System.out: state --- >>> []

I tried to explain as detailed as I can, I hope you can understand.

CodePudding user response:

A Flow doesn't do anything until you call a terminal operator on it. You called onEach, which is not a terminal operator. You should use collect. Or you can avoid the nesting inside a launch block by using onEach and launchIn, which does the same thing as launching a coroutine and calling collect() on the flow. You don't need to specify Dispatchers.IO here because nothing in your Flow is blocking. You correctly wrapped the blocking call in withContext(Dispatchers.IO), and the warning is a false positive. That's a well-known bug in their compiler inspection.

searchJob = getWordsAndMeaningsOnTheInternetUseCase().onEach { result ->

    when (result) {

        is Resource.Success -> {
            _state.value = state.value.copy(
                mostWordUsedItems = result.data ?: mutableListOf(),
                isLoading = false
            )
        }

        is Resource.Error -> {
            _state.value = state.value.copy(
                mostWordUsedItems = result.data ?: mutableListOf(),
                isLoading = false
            )
        }

        is Resource.Loading -> {
            _state.value = state.value.copy(
                mostWordUsedItems = result.data ?: mutableListOf(),
                isLoading = true
            )
        }
    }

}.launchIn(viewModelScope)

By the way, you need to move your emit(Success...) inside your try block. The way it is now, when there is an error, the error will immediately get replaced by a Success with empty list.

Side note, I recommend avoiding passing MutableLists around between classes. You have no need for them and it's a code smell. Sharing mutable state between classes is error-prone. I don't think there is any justification for using a Flow<MutableList> instead of a Flow<List>.

You rarely even need a MutableList locally in a function. For example, you could have done in your try block:

val listData = List(eventsSize) {
    val row = rows[it]
    val cols = row.select("td")
    val word = cols[0].text()
    val meaning = cols[1].text()

    ExtractedData(i,word,meaning)
}
emit(Resource.Success(listData))
  • Related