I've view model with MutableLiveData that look like this:
private val _num = MutableLiveData<Float>()
val num: LiveData<Float>
get() = _num
val sum: Int= sumMap.map { it.value }.sumBy { it.num}
_num.value = sum.toFloat()
I'm able to get the value correctly, but when I tried to access through the viewmodel objec, I get
kotlin.KotlinNullPointerException
Why I'm getting this error? my code in my fragment look like this:
val dVal: Float = viewModel.num.value!!
Log.i("MainScreenFragment", "numVal $dVal")
Also, If I remove the !! assertion, I get required Float but found Float?, Why is this? I didn't declare my variable at any point as nullable
EDIT:
This is the entire function, this function called inside init block
private fun getPlacesCountries() {
coroutineScope.launch {
var getPlacesDeffers = PlaceApi.retrofitService.getPropeties()
try {
var result = getPlacesDeffers.await()
if (result.isNotEmpty()) {
val sumMap: Map<String, Data> = result.mapValues { (country,data) -> data.last() }
val sum: Int= sumMap.map { it.value }.sumBy { it.num }
_num.value = sum.toFloat()
}
}
} catch (e: Exception) {
_response.value = "Failure: ${e.message}"
}
}
}
In your activity/fragment you need to observe LiveData:
viewModel.num.observe(this, Observer {
it?.let { chart.setData(it) } // set values if it is not null
})
Related
I'm trying to create this MockController with mockk to avoid create a new class for testing.
Is possible to do that?
class MockController : IController {
override lateinit var output: (String) -> Unit
override fun start() {
output("OK")
}
}
Class to test:
class ClassToTest(
private val controller: IController,
private val output: (String) -> Unit
){
fun start() {
controller.output = { result ->
output(result)
}
controller.start()
}
}
Then I use like this TEST example:
#Test
fun checkOutputIsCalled() {
runBlocking {
var outputCalled = false
val outputClassToTest: (String) -> Unit = {
outputCalled = true
}
val classToTest = ClassToTest(MockController(), outputClassToTest)
classToTest.start()
delay(1000)
assert(outputCalled)
}
}
I'm trying to update:
#Test
fun checkOutputIsCalled() {
runBlocking {
val controller = spyk<IController>()
var outputCalled = false
val outputClassToTest: (String) -> Unit = {
outputCalled = true
}
val classToTest = ClassToTest(controller, outputClassToTest)
every { controller.start() } answers {
controller.output.invoke("OK")
} //When I execute the test, output is null because yet doesn't exist the output creted inside ClassToTest
classToTest.start()
delay(1000)
assert(outputCalled)
}
}
When I execute the test, output is null because yet doesn't exist the output creted inside ClassToTest
How this could be after the output assign?
Thanks!
You should mock your output object and your Controller. Once done, tell your mocked controller to return the mocked output when property is called. Right after the start() invocation you can verify that output lambda was invoked. Please note that all your mocks must be relaxed.
class ClassToTestTesting {
companion object {
const val INVOCATION_PARAM = "OK"
}
#Test
fun test() = runBlocking {
val paramOutput: (String) -> Unit = mockk(relaxed = true)
val controller: IController = mockk(relaxed = true) {
every { output } returns paramOutput
every { start() } answers { output.invoke(INVOCATION_PARAM) }
}
val classToTest = ClassToTest(
controller,
paramOutput
)
classToTest.start()
verify { paramOutput(INVOCATION_PARAM) }
}
}
I have a data class for data that come from user entries. İt is carrying this data to Firebase. This data class also includes documentId variable which is a empty string by default. I want to add document Id's that Firebase created automatically. I tried every way I could think of. But it takes default value in any way.
Here are the four code snippets about this issue. Data class, adding data activity, and retrieving data activity and their View Models.
Dataclass:
data class AnalyzeModel(
var concept: String?="",
var reason: String?="",
var result: String?="",
var rrRatio: Double?=0.0,
var tarih: Timestamp=Timestamp.now(),
var tradingViewUrl: String="",
var id : String="")
AddAnalyzeActivity, addData function:
fun addData(view: View) {
val tarih = com.google.firebase.Timestamp.now()
val rr = rrText.text.toString()
var doubleRR = rr.toDoubleOrNull()
if (doubleRR == null) { doubleRR = 0.0 }
val analyzeDTO = AnalyzeModel(
conceptText.text.toString(),
reasonForText.text.toString(),
resultAddingText.text.toString(),
doubleRR,
tarih,
chartImage.text.toString()
)
viewModel.save(analyzeDTO)
val intent = Intent(this, PairDetailActivity::class.java)
startActivity(intent)
finish()
}
AddAnalyze ViewModel, save function:
fun save(data: AnalyzeModel) {
database.collection(dbCollection!!).document("Specified").collection("Pairs")
.document(chosenPair!!)
.collection("Analysis")
.add(data)
.addOnFailureListener { exception ->
exception.printStackTrace()
Toast.makeText(getApplication(), exception.localizedMessage, Toast.LENGTH_LONG).show()
}
}
PairViewModel, retrieveData function:
private fun retrieveData() {
val docRef = collectionRef.orderBy("tarih", Query.Direction.DESCENDING)
docRef.addSnapshotListener { value, error ->
try {
if (value != null && !value.isEmpty) {
val allAnalysis= ArrayList<AnalyzeModel>()
val documents = value.documents
documents.forEach {
val analyze = it.toObject(AnalyzeModel::class.java)
if (analyze!=null){
allAnalysis.add(analyze)
}
}
list.value = allAnalysis
} else if (error != null) {
Toast.makeText(Application(), error.localizedMessage, Toast.LENGTH_LONG).show()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
I want to add document IDs that Firebase created automatically.
To solve this, you only need to annotate the field with #DocumentId.
data class AnalyzeModel(
var concept: String?="",
var reason: String?="",
var result: String?="",
var rrRatio: Double?=0.0,
var tarih: Timestamp=Timestamp.now(),
var tradingViewUrl: String="",
#DocumentId 👈
var id : String=""
)
Be also sure to have the latest version of Firestore.
I am trying to get list of todos from database with livedata however, while debugging it always shows null for value. I have provided my files below.
My Dao:
#Query("SELECT * FROM todo_table WHERE IIF(:isCompleted IS NULL, 1, isCompleted = :isCompleted)")
fun getTodos(isCompleted: Boolean?): LiveData<List<Todo>>
My ViewModel:
private var _allTodoList = MutableLiveData<List<Todo>>()
var allTodoList: LiveData<List<Todo>> = _allTodoList
init {
viewModelScope.launch(Dispatchers.IO) {
val list = todoRepository.getTodos(null)
_allTodoList.postValue(list.value)
}
}
fun onFilterClick(todoType: Constants.TodoType) {
when (todoType) {
Constants.TodoType.ALL -> {
viewModelScope.launch(Dispatchers.IO) {
val list = todoRepository.getTodos(null)
_allTodoList.postValue(list.value)
}
}
Constants.TodoType.COMPLETED -> {
viewModelScope.launch(Dispatchers.IO) {
val list = todoRepository.getTodos(true)
_allTodoList.postValue(list.value)
}
}
Constants.TodoType.INCOMPLETE -> {
viewModelScope.launch(Dispatchers.IO) {
val list = todoRepository.getTodos(false)
_allTodoList.postValue(list.value)
}
}
}
}
My MainActivity:
val allTodoList = viewModel.allTodoList.observeAsState()
allTodoList.value?.run {//value is always null
if (!isNullOrEmpty()) {
...
} else {
...
}
}
While debugging I found that allTodoList.value is always null however, when I manually run same query in app inspection I the get the desired results.
You can simplify your code, see if it works.
ViewModel only needs this:
val allTodoList: LiveData<List<Todo>> = todoRepository.getTodos(null)
MainActivity:
val allTodoList by viewModel.allTodoList.observeAsState()
if (!allTodoList.isNullOrEmpty()) {
...
} else {
...
}
You are not observing the LiveData you get from Room.
YourDao.getTodos() and LiveData.getValue() are not suspend functions, so you get the current value, which is null because Room has not yet fetched the values from SQLite.
A possible solution would be to set the todo type as a live data itself and use a switchMap transformation in the ViewModel :
private val todoType = MutableLiveData<Constants.TodoType>(Constants.TodoType.ALL)
val allTodoList: LiveData<List<Todo>> = androidx.lifecycle.Transformations.switchMap(todoType) { newType ->
val typeAsBoolean = when(newType) {
Constants.TodoType.ALL -> null
Constants.TodoType.COMPLETED -> true
Constants.TodoType.INCOMPLETE -> false
else -> throw IllegalArgumentException("Not a possible value")
}
// create the new wrapped LiveData
// the transformation takes care of subscribing to it
// (and unsubscribing to the old one)
todoRepository.getTodos(typeAsBoolean)
}
fun onFilterClick(todoType: Constants.TodoType) {
// triggers the transformation
todoType.setValue(todoType)
}
This is in fact the exact use case demonstrated in the reference doc
I'm trying to insert separators to my list using the paging 3 compose library however, insertSeparators doesn't seem to indicate when we are at the beginning or end. My expectations are that before will be null at the beginning while after will be null at the end of the list. But it's never null thus hard to know when we are at the beginning or end. Here is the code:
private val filterPreferences =
MutableStateFlow(HomePreferences.FilterPreferences())
val games: Flow<PagingData<GameModel>> = filterPreferences.flatMapLatest {
useCase.execute(it)
}.map { pagingData ->
pagingData.map { GameModel.GameItem(it) }
}.map {
it.insertSeparators {before,after->
if (after == null) {
return#insertSeparators null
}
if (before == null) {
Log.i(TAG, "before is null: ") // never reach here
return#insertSeparators GameModel.SeparatorItem("title")
}
if(condition) {
GameModel.SeparatorItem("title")
}
else null
}
}
.cachedIn(viewModelScope)
GamesUseCase
class GamesUseCase #Inject constructor(
private val executionThread: PostExecutionThread,
private val repo: GamesRepo,
) : FlowUseCase<HomePreferences, PagingData<Game>>() {
override val dispatcher: CoroutineDispatcher
get() = executionThread.io
override fun execute(params: HomePreferences?): Flow<PagingData<Game>> {
val preferences = params as HomePreferences.FilterPreferences
preferences.apply {
return repo.fetchGames(query,
parentPlatforms,
platforms,
stores,
developers,
genres,
tags)
}
}
}
FlowUseCase
abstract class FlowUseCase<in Params, out T>() {
abstract val dispatcher: CoroutineDispatcher
abstract fun execute(params: Params? = null): Flow<T>
operator fun invoke(params: Params? = null) = execute(params).flowOn(dispatcher)
}
Here is the dependency :
object Pagination {
object Version {
const val pagingCompose = "1.0.0-alpha14"
}
const val pagingCompose = "androidx.paging:paging-compose:${Version.pagingCompose}"
}
I'm assuming that filterPreferences gives you Flow of some preference and useCase.execute returns Flow<PagingData<Model>>, correct?
I believe that the problem is in usage of flatMapLatest - it mixes page events of multiple useCase.execute calls together.
You should do something like this:
val games: Flow<Flow<PagingData<GameModel>>> = filterPreferences.mapLatest {
useCase.execute(it)
}.mapLatest {
it.map { pagingData -> pagingData.map { GameModel.GameItem(it) } }
}.mapLatest {
it.map { pagingData ->
pagingData.insertSeparators { before, after -> ... }
} // .cachedIn(viewModelScope)
}
This same structure works for us very well. I'm only not sure how cachedIn will work here, we are using a different caching mechanism, but you can try.
In ViewModel:
val drillerCatList: List<Int> = emptyList()
val shownCategoriesFlow = wordDao.getShownCategories() // which returns type Flow<List<CategoryItem>>
Catigory object:
data class CategoryItem(
val categoryName: String,
val categoryNumber: Int,
val categoryShown: Boolean = false,
#PrimaryKey(autoGenerate = true) var id: Int = 0
) : Parcelable {
}
How can I retrieve all categoryNumber values from shownCategoriesFlow: FLow<List> and fill drillerCatList: List with these values in ViewModel?
First make drillerCatList mutable as shown below:
val drillerCatList: ArrayList<Int> = ArrayList()
Now collect the list from the shownCategoriesFlow:
shownCategoriesFlow.collect {
it.forEach{ categoryItem ->
drillerCatList.add(categoryItem.categoryNumber)
}
}
First, your property either needs to be a MutableList or a var. Usually, var with a read-only List is preferable since it is less error-prone. Then you call collect on your Flow in a coroutine. collect behaves sort of like forEach does on an Iterable, except that it doesn't block the thread in between when elements are ready.
val drillerCatList: List<Int> = emptyList()
val shownCategoriesFlow = wordDao.getShownCategories()
init { // or you could put this in a function do do it passively instead of eagerly
viewModelScope.launch {
shownCategoriesFlow.collect { drillerCatList = it.map(CategoryItem::categoryNumber) }
}
}
Alternate syntax:
init {
shownCategoriesFlow
.onEach { drillerCatList = it.map(CategoryItem::categoryNumber) }
.launchIn(viewModelScope)
}