Events in javafx on kotlin dont work in function Application::Start - kotlin

I want to bind an event to keyboard
class ObjectApplication : Application() {
private val habitat = Habitat()
override fun start(stage: Stage) {
val root = Group()
val scene = Scene(root,Habitat.height,Habitat.width,Color.BLACK)
val rightCornerImg =
Image(ObjectApplication::class.java.getResource("Ricardo.png").toString())
initStage(stage,rightCornerImg,scene)
initText(root)
initKeyHandler(root,scene)
scene.onKeyPressed = EventHandler {
fun handle(event : KeyEvent) : Unit {
when(event.code){
KeyCode.B -> startSimulation(root)
KeyCode.E -> stopSimulation()
KeyCode.T -> toggleTime()
}
}
}
}
}
But functions bound to KeyCode are never called when I type B/E/T

Related

Will the operation of collect from Flow cost many system resources when I use Compose with Kotlin?

The SoundViewModel is a ViewModel class, and val listSoundRecordState may be used by some modules in the App.
In Code A, I invoke fun collectListSoundRecord() when I need to use the data listSoundRecordState. But fun collectListSoundRecord() may be launched again and again because of Jetpack Compose recomposition, I don't know if it will cost many system resources?
In Code B, I launch private fun collectListSoundRecord() in init { }, collectListSoundRecord() will be launched only one time, but it will persist in memory until the App code closed even if I needn't to use the data listSoundRecordState, will the way cost many system resources?
Code A
#HiltViewModel
class SoundViewModel #Inject constructor(
...
): ViewModel() {
private val _listSoundRecordState = MutableStateFlow<Result<List<MRecord>>>(Result.Loading)
val listSoundRecordState = _listSoundRecordState.asStateFlow()
init { }
//It may be launched again and again
fun collectListSoundRecord(){
viewModelScope.launch {
listRecord().collect {
result -> _listSoundRecordState.value =result
}
}
}
private fun listRecord(): Flow<Result<List<MRecord>>> {
return aSoundMeter.listRecord()
}
}
Code B
#HiltViewModel
class SoundViewModel #Inject constructor(
...
): ViewModel() {
private val _listSoundRecordState = MutableStateFlow<Result<List<MRecord>>>(Result.Loading)
val listSoundRecordState = _listSoundRecordState.asStateFlow()
init { collectListSoundRecord() }
private fun collectListSoundRecord(){
viewModelScope.launch {
listRecord().collect {
result -> _listSoundRecordState.value =result
}
}
}
private fun listRecord(): Flow<Result<List<MRecord>>> {
return aSoundMeter.listRecord()
}
}
You would probably benefit from collecting the original flow (from listRecord()) only when there is a subscriber to your intermediate flow (the one you keep in your SoundViewModel) and cache the results.
A subscriber, in your case, would be a Composable function that collects the values (and may recompose often).
You can achieve this using the non-suspending variant of stateIn(), since you have a default value.
#HiltViewModel
class SoundViewModel #Inject constructor(
...
): ViewModel() {
val listSoundRecordState = listRecord().stateIn(viewModelScope, SharingStarted.WhileSubscribed(), Result.Loading)
private fun listRecord(): Flow<Result<List<MRecord>>> {
return aSoundMeter.listRecord()
}
}
In order to use the StateFlow from the UI layer (a #Composable function), you will have to transform it into a State, like so:
val viewModel: SoundViewModel = viewModel()
val listSoundRecord = viewModel.listSoundRecordState.collectAsState()
I use the below implementation of composable with view-model using LaunchedEffect
PokemonDetailScreen.kt
#Composable
fun PokemonDetailScreen(
dominantColor: Color,
pokemonName: String,
navController: NavController,
topPadding: Dp = 20.dp,
pokemonImageSize: Dp = 200.dp,
viewModel: PokemonDetailVm = hiltViewModel()
) {
var pokemonDetailData by remember { mutableStateOf<PokemonDetailView>(PokemonDetailView.DisplayLoadingView) }
val pokemonDetailScope = rememberCoroutineScope()
LaunchedEffect(key1 = true){
viewModel.getPokemonDetails(pokemonName)
viewModel.state.collect{ pokemonDetailData = it }
}
Box(
modifier = Modifier
.fillMaxSize()
.background(dominantColor)
.padding(bottom = 16.dp)
) {
PokemonHeader(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(0.2f)
.align(Alignment.TopCenter)
) {
navController.popBackStack()
}
PokemonBody(
pokemonInfo = pokemonDetailData,
topPadding = topPadding,
pokemonImageSize = pokemonImageSize
) {
pokemonDetailScope.launch {
viewModel.getPokemonDetails(pokemonName)
}
}
Box(
contentAlignment = Alignment.TopCenter,
modifier = Modifier
.fillMaxSize()
) {
if(pokemonDetailData is PokemonDetailView.DisplayPokemonView){
val data = (pokemonDetailData as PokemonDetailView.DisplayPokemonView).data
data.sprites.let {
// Image is available
val url = PokemonUtils.formatPokemonDetailUrl(it.frontDefault)
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(url)
.crossfade(true)
.build(),
contentDescription = data.name,
contentScale = ContentScale.Fit,
modifier = Modifier
// Set the default size passed to the composable
.size(pokemonImageSize)
// Shift the image down from the top
.offset(y = topPadding)
)
}
}
}
}
}
PokemonDetailVm.kt
#HiltViewModel
class PokemonDetailVm #Inject constructor(
private val repository: PokemonRepositoryFeature
): ViewModel(){
private val _state = MutableSharedFlow<PokemonDetailView>()
val state = _state.asSharedFlow()
suspend fun getPokemonDetails(pokemonName:String) {
viewModelScope.launch {
when(val pokemonInfo = repository.getPokemonInfo(pokemonName)){
is Resource.Error -> {
pokemonInfo.message?.let {
_state.emit(PokemonDetailView.DisplayErrorView(message = it))
}
}
is Resource.Loading -> {
_state.emit(PokemonDetailView.DisplayLoadingView)
}
is Resource.Success -> {
pokemonInfo.data?.let {
_state.emit(PokemonDetailView.DisplayPokemonView(data = it))
}
}
}
}
}
}

Method param require nothing instead of typed param

There's superclass with typed parameter:
abstract inner class CalendarViewHolder<T : CalendarItem>(view: View) :
RecyclerView.ViewHolder(view) {
open fun bindData(data: T) {}
}
I expect that the method bindData will accept params which are objects of CalendarItem subclasses.
But in onBindViewHolder I got the following error:
Type mismatch.
Required:
Nothing
Found:
CalendarItem
Here's the implementation of the method:
override fun onBindViewHolder(holder: CalendarViewHolder<out CalendarItem>, position: Int) {
val item = items[position]
holder.bindData(item)
if (item is DayItem) {
if (holder is OnClickStrategy) {
holder.onClickListener = {
selectedDayOfYear = item.date?.get(Calendar.DAY_OF_YEAR) ?: 0
notifyItemChanged(position)
if (previousClickedPosition != -1) {
notifyItemChanged(previousClickedPosition)
}
previousClickedPosition = holder.adapterPosition
}
}
}
}
Here's the full code of adapter:
class CalendarAdapter :
RecyclerView.Adapter<CalendarAdapter.CalendarViewHolder<out CalendarItem>>() {
private val items = mutableListOf<CalendarItem>()
private var selectedDayOfYear = -1
private var previousClickedPosition = -1
abstract inner class CalendarViewHolder<T : CalendarItem>(view: View) :
RecyclerView.ViewHolder(view) {
open fun bindData(data: T) {}
}
abstract inner class CalendarDateViewHolder(view: View) : CalendarViewHolder<DayItem>(view) {
protected lateinit var tvDate: AppCompatTextView
protected lateinit var tvMonth: AppCompatTextView
protected lateinit var tvDayDescription: AppCompatTextView
protected lateinit var clRoot: ConstraintLayout
#CallSuper
override fun bindData(data: DayItem) {
tvDate = itemView.findViewById(R.id.tv_day_number)
tvMonth = itemView.findViewById(R.id.tv_month)
tvDayDescription = itemView.findViewById(R.id.tv_day_description)
clRoot = itemView.findViewById(R.id.cl_root)
}
}
interface OnClickStrategy {
var onClickListener: () -> Unit
}
inner class CalendarEmptyViewHolder(view: View) : CalendarViewHolder<EmptyDayItem>(view)
inner class CalendarWithDateViewHolder(view: View) :
CalendarDateViewHolder(view), OnClickStrategy {
override lateinit var onClickListener: () -> Unit
override fun bindData(data: DayItem) {
super.bindData(data)
tvDate.text = data.date?.get(Calendar.DAY_OF_MONTH)?.toString()
clRoot.setOnClickListener {
onClickListener.invoke()
}
clRoot.background = ContextCompat.getDrawable(
itemView.context,
when (selectedDayOfYear) {
data.date?.get(Calendar.DAY_OF_YEAR) -> R.drawable.item_selected_background
else -> R.drawable.item_default_background
}
)
tvDate.setTextColor(
ContextCompat.getColor(
itemView.context,
if (selectedDayOfYear == data.date?.get(Calendar.DAY_OF_YEAR)) R.color.white
else R.color.black
)
)
}
}
inner class CalendarTodayDateViewHolder(view: View) : CalendarDateViewHolder(view) {
override fun bindData(data: DayItem) {
super.bindData(data)
tvDayDescription.text = "hoy"
tvDate.text = data.date?.get(Calendar.DAY_OF_MONTH)?.toString()
clRoot.background = ContextCompat.getDrawable(
itemView.context,
R.drawable.item_today_background
)
val monthName = when (data.date?.get(Calendar.MONTH)) {
0 -> "January"
1 -> "February"
else -> "March"
}
tvMonth.text = monthName
tvDate.setTextColor(ContextCompat.getColor(itemView.context, R.color.white))
tvDayDescription.setTextColor(ContextCompat.getColor(itemView.context, R.color.white))
tvMonth.setTextColor(ContextCompat.getColor(itemView.context, R.color.white))
}
}
inner class CalendarNewMonthDateViewHolder(view: View) : CalendarDateViewHolder(view),
OnClickStrategy {
override lateinit var onClickListener: () -> Unit
override fun bindData(data: DayItem) {
super.bindData(data)
tvDate.text = data.date?.get(Calendar.DAY_OF_MONTH)?.toString()
val monthName = when (data.date?.get(Calendar.MONTH)) {
0 -> "January"
1 -> "February"
else -> "March"
}
tvMonth.text = monthName
clRoot.setOnClickListener {
onClickListener.invoke()
}
tvDate.setTextColor(
ContextCompat.getColor(
itemView.context,
if (selectedDayOfYear == data.date?.get(Calendar.DAY_OF_YEAR)) R.color.white
else R.color.black
)
)
clRoot.background = ContextCompat.getDrawable(
itemView.context,
when (selectedDayOfYear) {
data.date?.get(Calendar.DAY_OF_YEAR) -> R.drawable.item_selected_background
else -> R.drawable.item_default_background
}
)
}
}
inner class CalendarDisabledDateViewHolder(view: View) : CalendarDateViewHolder(view) {
override fun bindData(data: DayItem) {
super.bindData(data)
tvDate.text = data.date?.get(Calendar.DAY_OF_MONTH)?.toString()
clRoot.background = ContextCompat.getDrawable(
itemView.context,
R.drawable.item_disabled_background
)
tvDate.setTextColor(ContextCompat.getColor(itemView.context, R.color.silver))
}
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
) = when (viewType) {
CALENDAR_EMPTY_ITEM_TYPE -> onCreateCalendarEmptyViewHolder(parent)
CALENDAR_TODAY_DATE_ITEM_TYPE -> onCreateCalendarTodayDateViewHolder(parent)
CALENDAR_NEW_MONTH_DATE_ITEM_TYPE -> onCreateCalendarNewMonthDateViewHolder(parent)
CALENDAR_WITH_DATE_ITEM_TYPE -> onCreateCalendarWithDateViewHolder(parent)
else -> onCreateCalendarDisabledDateViewHolder(parent)
}
override fun getItemViewType(position: Int) = when (val item = items[position]) {
EmptyDayItem -> CALENDAR_EMPTY_ITEM_TYPE
is DayItem -> when {
item.isToday() -> CALENDAR_TODAY_DATE_ITEM_TYPE
item.isNewMonth() -> CALENDAR_NEW_MONTH_DATE_ITEM_TYPE
item.isDateEnabled -> CALENDAR_WITH_DATE_ITEM_TYPE
else -> CALENDAR_DISABLED_DATE_ITEM_TYPE
}
else -> UNKNOWN_ITEM_TYPE
}
private fun onCreateCalendarWithDateViewHolder(parent: ViewGroup) = CalendarWithDateViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.item_calendar_day, parent, false)
)
private fun onCreateCalendarEmptyViewHolder(parent: ViewGroup): CalendarViewHolder<EmptyDayItem> =
CalendarEmptyViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.item_calendar_empty, parent, false)
)
private fun onCreateCalendarTodayDateViewHolder(parent: ViewGroup) =
CalendarTodayDateViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.item_calendar_day, parent, false)
)
private fun onCreateCalendarNewMonthDateViewHolder(parent: ViewGroup) =
CalendarNewMonthDateViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.item_calendar_day, parent, false)
)
private fun onCreateCalendarDisabledDateViewHolder(parent: ViewGroup) =
CalendarDisabledDateViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.item_calendar_day, parent, false)
)
override fun onBindViewHolder(holder: CalendarViewHolder<out CalendarItem>, position: Int) {
val item = items[position]
holder.bindData(item)
if (item is DayItem) {
if (holder is OnClickStrategy) {
holder.onClickListener = {
selectedDayOfYear = item.date?.get(Calendar.DAY_OF_YEAR) ?: 0
notifyItemChanged(position)
if (previousClickedPosition != -1) {
notifyItemChanged(previousClickedPosition)
}
previousClickedPosition = holder.adapterPosition
}
}
}
}
override fun getItemCount() = items.size
fun setItems(items: MutableList<DayItem>) {
val localItems: MutableList<CalendarItem> = items.toMutableList()
if (!items.isNullOrEmpty()) {
val firstDayNumber = items[0].date?.get(Calendar.DAY_OF_WEEK) ?: 0
for (i in 1 until firstDayNumber) {
localItems.add(0, EmptyDayItem)
}
this.items.clear()
this.items.addAll(localItems)
}
}
companion object {
private const val CALENDAR_EMPTY_ITEM_TYPE = 0
private const val CALENDAR_TODAY_DATE_ITEM_TYPE = 1
private const val CALENDAR_NEW_MONTH_DATE_ITEM_TYPE = 2
private const val CALENDAR_WITH_DATE_ITEM_TYPE = 3
private const val CALENDAR_DISABLED_DATE_ITEM_TYPE = 4
private const val CALENDAR_HEADER_ITEM_TYPE = 5
private const val UNKNOWN_ITEM_TYPE = -1
}
}
sealed class CalendarItem
class DayItem(
val date: Calendar? = null,
val isDateEnabled: Boolean = true
): CalendarItem() {
fun isToday() = if (date != null) isSameDay(date, Calendar.getInstance()) else false
fun isNewMonth() = date?.get(Calendar.DAY_OF_MONTH) == 1
private fun isSameDay(cal1: Calendar, cal2: Calendar) =
cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA) &&
cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) &&
cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR)
}
object EmptyDayItem : CalendarItem()
What's the reason of the problem?
You specified the type as out CalendarItem, but the bind function consumes the item of type T, so it is in an in position, not out. So by marking it as out at the class declaration, you have restricted it to Nothing wherever T appears as a function parameter type.
There's no way to make generics work for you here, as I'm assuming you're trying to get it to allow you to bind items without casting. onCreateViewHolder has your ViewHolder in an out position, which means your particular ViewHolder cannot have invariant or contravariant (in) type if you want to be able to return different types of your ViewHolder subclass from onCreateViewHolder.
It's just not logically possible for generics to safely allow you to pass these items to the appropriate types, and you will have to use casting.
I suggest you eliminate the generic type from your abstract ViewHolder, and make each subclass throw an error if it gets the wrong type. Then you will quickly get exceptions when you test your app if there are any type mismatches and you will know the problem lies in either getItemViewType or onCreateViewHolder, where you've hooked up your classes and types incorrectly.
I also suggest using View Binding, because it will make these classes much more streamlined. No lateinit view properties or findViewById calls.
For example:
abstract class CalendarViewHolder(view: View): RecyclerView.ViewHolder(view) {
abstract fun bindData(data: CalendarItem): Any
// Any return type here allows subclasses to use "= with(binding)"
// to reduce nesting.
}
class CalendarDateViewHolder(private val binding: ItemCalendarDayBinding): MyViewHolder(binding.root) {
override fun bind(item: CalendarItem) = with(binding) {
item as? DayItem ?: error("Wrong item type.")
// item is now smart cast as DayItem
// bind the DayItem data to the views.
// can access the views directly by name in with block.
}
}
private fun onCreateCalendarWithDateViewHolder(parent: ViewGroup) = CalendarWithDateViewHolder(
ItemCalendarDayBinding.inflate(LayoutInflater.from(parent.context), parent, false)
)
And I suggest also getting rid of all those onCreate functions and adding constructors that use the parent ViewGroup, just to streamline things, for example:
class CalendarDateViewHolder private constructor(private val binding: ItemCalendarDayBinding): MyViewHolder(binding.root) {
constructor(parent: ViewGroup): this(ItemCalendarDayBinding.inflate(LayoutInflater.from(parent.context), parent, false))
//...
}

Observer pattern is not working in Android MVVM

I am trying to update my view according to my data in my ViewModel, using MVVM
I need in the method onCacheReception to update my map whenever zones is changing
ViewModel
class MainViewModel constructor(application: Application) : AndroidViewModel(application),
CacheListener {
private val instance = Initializer.getInstance(application.applicationContext)
private val _zones = MutableLiveData<List<Zone>>()
val zones: LiveData<List<Zone>>
get() = _zones
init {
CacheDispatcher.addCacheListener(this)
}
override fun onCacheReception() {
val zonesFromDB: List<Zone>? = instance.fetchZonesInDatabase()
_zones.value = zonesFromDB
}
}
MainActivity
class MainActivity : AppCompatActivity(), EasyPermissions.PermissionCallbacks, OnMapReadyCallback {
private val mainViewModel: MainViewModel = ViewModelProvider(this).get(MainViewModel(application)::class.java)
private lateinit var initializer: Initializer
private lateinit var map: GoogleMap
private val REQUEST_CODE_LOCATIONS: Int = 100
private val permissionLocationsRationale: String = "Permissions for Fine & Coarse Locations"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (checkForLocationsPermission()) {
setUp()
mapSetUp()
}
mainViewModel.zones.observe(this, Observer { zones ->
zones.forEach {
Log.i("YES DATA", "Data has been updated")
val latLng = it.lat?.let { it1 -> it.lng?.let { it2 -> LatLng(it1, it2) } }
val markerOptions = latLng?.let { it1 -> MarkerOptions().position(it1) }
map.addMarker(markerOptions)
}
})
}
My Log is never displaying and it doesn't seem while debugging that mainView.zones.observe { } is called when I receive some new data in my ViewModel
In the onCacheReception(), replace:
_zones.value = zonesFromDB
by:
_zones.postValue(zonesFromDB)
in case your onCacheReception() function is called from a worker thread.

Listening to coroutine from view cant be done from the views init

I am trying to listen to my ViewModels MutableStateFlow from my FlutterSceneView. But I get the following error when trying to set the listener from the views init:
Suspend function 'listenToBackgroundColor' should be called only from a coroutine or another suspend function
class FlutterSceneView(context: Context, private val viewModel: FlutterSceneViewModelType): PlatformView {
private val context = context
private val sceneView = SceneView(context)
init {
listenToBackgroundColor() // Error here
}
private suspend fun listenToBackgroundColor() {
viewModel.colorFlow.collect {
val newColor = Color.parseColor(it)
sceneView.setBackgroundColor(newColor)
}
}
}
My ViewModel:
interface FlutterSceneViewModelType {
var colorFlow: MutableStateFlow<String>
}
class FlutterSceneViewModel(private val database: Database): FlutterSceneViewModelType, ViewModel() {
override var colorFlow = MutableStateFlow<String>("#FFFFFF")
init {
listenToBackgroundColorFlow()
}
private fun listenToBackgroundColorFlow() {
database.backgroundColorFlow.watch {
colorFlow.value = it.hex
}
}
}
the .watch call is a helper I have added so that this can be exposed to iOS using Kotlin multi-platform, it looks as follows but I can use collect instead if necessary:
fun <T> Flow<T>.asCommonFlow(): CommonFlow<T> = CommonFlow(this)
class CommonFlow<T>(private val origin: Flow<T>) : Flow<T> by origin {
fun watch(block: (T) -> Unit): Closeable {
val job = Job()
onEach {
block(it)
}.launchIn(CoroutineScope(Dispatchers.Main + job))
return object : Closeable {
override fun close() {
job.cancel()
}
}
}
}
I resolved this by using viewModel context:
private fun listenToBackgroundColor() {
viewModel.colorFlow.onEach {
val newColor = Color.parseColor(it)
sceneView.setBackgroundColor(newColor)
}.launchIn(viewModel.viewModelScope)
}
I had to import the following into my ViewModel:
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
from:
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0")

Test a view model with livedata, coroutines (Kotlin)

I've been trying to test my view model for several days without success.
This is my view model :
class AdvertViewModel : ViewModel() {
private val parentJob = Job()
private val coroutineContext: CoroutineContext
get() = parentJob + Dispatchers.Default
private val scope = CoroutineScope(coroutineContext)
private val repository : AdvertRepository = AdvertRepository(ApiFactory.Apifactory.advertService)
val advertContactLiveData = MutableLiveData<String>()
fun fetchRequestContact(requestContact: RequestContact) {
scope.launch {
val advertContact = repository.requestContact(requestContact)
advertContactLiveData.postValue(advertContact)
}
}
}
This is my repository :
class AdvertRepository (private val api : AdvertService) : BaseRepository() {
suspend fun requestContact(requestContact: RequestContact) : String? {
val advertResponse = safeApiCall(
call = {api.requestContact(requestContact).await()},
errorMessage = "Error Request Contact"
)
return advertResponse
}
}
This is my view model test :
#RunWith(JUnit4::class)
class AdvertViewModelTest {
private val goodContact = RequestContact(...)
private lateinit var advertViewModel: AdvertViewModel
private var observer: Observer<String> = mock()
#get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
#Before
fun setUp() {
advertViewModel = AdvertViewModel()
advertViewModel.advertContactLiveData.observeForever(observer)
}
#Test
fun fetchRequestContact_goodResponse() {
advertViewModel.fetchRequestContact(goodContact)
val captor = ArgumentCaptor.forClass(String::class.java)
captor.run {
verify(observer, times(1)).onChanged(capture())
assertEquals("someValue", value)
}
}
}
The method mock() :
inline fun <reified T> mock(): T = Mockito.mock(T::class.java)
I got this error :
Wanted but not invoked: observer.onChanged();
-> at com.vizzit.AdvertViewModelTest.fetchRequestContact_goodResponse(AdvertViewModelTest.kt:52)
Actually, there were zero interactions with this mock.
I don't understand how to retrieve the result of my query.
You would need to write a OneTimeObserver to observe livedata from the ViewModel
class OneTimeObserver<T>(private val handler: (T) -> Unit) : Observer<T>, LifecycleOwner {
private val lifecycle = LifecycleRegistry(this)
init {
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
}
override fun getLifecycle(): Lifecycle = lifecycle
override fun onChanged(t: T) {
handler(t)
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
}
}
After that you can write an extension function:
fun <T> LiveData<T>.observeOnce(onChangeHandler: (T) -> Unit) {
val observer = OneTimeObserver(handler = onChangeHandler)
observe(observer, observer)
}
Than you can check this ViewModel class class that I have from a project to check what's going on with your LiveData after you act (when) with invoking a method.
As for your error, it just says that the onChanged() method is not being called ever.