Android paging 3 library removing .cahcedIn(viewModelScope) will throw exception - android

I am working with paging 3.0.0-alpha11 library in my app.
There is a fragment that has a view model. in the view model I implemented paging data like this. there is one mutable live data for a parameter named promoId. promoId is a string that represents id of a model. i use it with another parameter named page to get a page of data from network. so the problem is that the first time i navigate to te fragment every thing works fine, but after popping the fragment and navigating to it for the second time, I get an error and app crashes.
my fragment code.
class ExploreFragment : DaggerFragment() {
private var _binding: FragmentExploreBinding? = null
private val binding get() = _binding!!
#Inject
lateinit var viewModelProviderFactory: ViewModelProviderFactoryImpl
private lateinit var exploreViewModel: ExploreViewModel
lateinit var commentsAdapter: LiveCommentAdapter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
initLists()
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
exploreViewModel =
ViewModelProvider(this, viewModelProviderFactory)[ExploreViewModel::class.java]
observeComments()
commentsViewModel.getPromoComments(promoId)
}
}
private fun initLists() {
commentsAdapter = LiveCommentAdapter()
binding.rvComments.adapter = commentsAdapter
binding.rvSearchResults.adapter = searchResultAdapter
}
private fun observeComments() {
exploreViewModel.commentsLiveData.observe(viewLifecycleOwner) { comments ->
commentsAdapter.submitData(viewLifecycleOwner.lifecycle, comments)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
This is my view model
also note that I don't use cachedIn(viewModelScope) after commentRepository.getCommentsByPromo(promoId?:"") because it caches the data and does not call load method of paging data for the second time.
class ExploreViewModel #Inject constructor(
#Inject #JvmField var commentRepository: CommentRepository,
) :
ViewModel() {
private val promoIdLiveData: MutableLiveData<String?> = MutableLiveData()
var commentsLiveData: LiveData<PagingData<Comment>> = promoIdLiveData.switchMap { promoId ->
commentRepository.getCommentsByPromo(promoId?:"")
}
fun getComments(promoId: String) {
promoIdLiveData.value = promoId
}
}
And this is my repository code.
class CommentRepository #Inject constructor(#Inject #JvmField var retrofit: Retrofit) {
private val api: CommentsApi = retrofit.create(CommentsApi::class.java)
fun getCommentsByPromo(promoId: String):LiveData<PagingData<Comment>> = Pager(
config = PagingConfig(pageSize = 20, enablePlaceholders = false,prefetchDistance = 100),
pagingSourceFactory = { PromoCommentPagingSource(promoId, api) }
).liveData
}
and the error i get is
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.tivasoft.project, PID: 28896
java.lang.IllegalStateException: Attempt to collect twice from pageEventFlow, which is an illegal operation. Did you forget to call Flow<PagingData<*>>.cachedIn(coroutineScope)?
at androidx.paging.PageFetcherSnapshot$pageEventFlow$1.invokeSuspend(PageFetcherSnapshot.kt:88)
at androidx.paging.PageFetcherSnapshot$pageEventFlow$1.invoke(Unknown Source:10)
at androidx.paging.CancelableChannelFlowKt$cancelableChannelFlow$1.invokeSuspend(CancelableChannelFlow.kt:35)
at androidx.paging.CancelableChannelFlowKt$cancelableChannelFlow$1.invoke(Unknown Source:10)
at kotlinx.coroutines.flow.ChannelFlowBuilder.collectTo$suspendImpl(Builders.kt:344)
at kotlinx.coroutines.flow.ChannelFlowBuilder.collectTo(Unknown Source:0)
at kotlinx.coroutines.flow.internal.ChannelFlow$collectToFun$1.invokeSuspend(ChannelFlow.kt:60)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.EventLoop.processUnconfinedEvent(EventLoop.common.kt:69)
at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:349)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:27)
at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:109)
at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:158)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:56)
at kotlinx.coroutines.BuildersKt.launch(Unknown Source:1)
at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(Builders.common.kt:49)
at kotlinx.coroutines.BuildersKt.launch$default(Unknown Source:1)
at androidx.paging.AsyncPagingDataDiffer.submitData(AsyncPagingDataDiffer.kt:156)
at androidx.paging.PagingDataAdapter.submitData(PagingDataAdapter.kt:172)
at com.tivasoft.project.ui.explore.ExploreFragment$observeComments$$inlined$observe$1.onChanged(LiveData.kt:52)
at androidx.lifecycle.LiveData.considerNotify(LiveData.java:131)
at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:144)
at androidx.lifecycle.LiveData$ObserverWrapper.activeStateChanged(LiveData.java:443)
at androidx.lifecycle.LiveData$LifecycleBoundObserver.onStateChanged(LiveData.java:395)
at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:361)
at androidx.lifecycle.LifecycleRegistry.forwardPass(LifecycleRegistry.java:300)
at androidx.lifecycle.LifecycleRegistry.sync(LifecycleRegistry.java:339)
at androidx.lifecycle.LifecycleRegistry.moveToState(LifecycleRegistry.java:145)
at androidx.lifecycle.LifecycleRegistry.handleLifecycleEvent(LifecycleRegistry.java:131)
at androidx.fragment.app.FragmentViewLifecycleOwner.handleLifecycleEvent(FragmentViewLifecycleOwner.java:51)
at androidx.fragment.app.Fragment.performStart(Fragment.java:2737)
at androidx.fragment.app.FragmentStateManager.start(FragmentStateManager.java:365)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1194)
at androidx.fragment.app.FragmentManager.addAddedFragments(FragmentManager.java:2224)
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1997)
at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1953)
at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1849)
at androidx.fragment.app.FragmentManager$4.run(FragmentManager.java:413)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
I would be very thankful if anybody can tell me what's wrong

I'm a bit confused on exactly what you want to happen, but my understanding is that you want to reload from scratch whenever you navigate back.
The error is saying you cannot call .submitData on the same instance of PagingData twice. You're essentially trying load cached data unintentionally and since you didn't call cachedIn, Paging is throwing an exception.
An easy way to force Paging to load from scratch every time, is to make sure you construct a new instance of Pager each time.
So I would try calling observeComments() in onViewCreated and turn commentsLiveData into a function (instead of a var) which returns a new LiveData<PagingData> each time its called.

In my case I had a Transformations.switchMap(liveData) and that was causing the issue, the fix was to make the PagingData to cache before the switchMap
fun observable(): LiveData<PagingData<SomeModel>> {
val pagingData = //obtained somewhere
//make sure to cache it
val chachedPagingData = pagingData.cachedIn(viewModelScope)
return Transformations.switchMap(otherLiveData) { observed ->
//use observed for obtaining live attributes needed
chachedPagingData.filter {...}.map{...}
}
}
For me, that was the solution because the pagingData sometimes can be suspend, so the above would be more explanatory like this:
suspend fun observable(): LiveData<PagingData<SomeModel>> {
val someAttributeFromTheDb = //get it using ROOM
val pagingData = if (someAttributeFromTheDb == /*condition*/) {
suspendPagingData } else regularPagingData
val chachedPagingData = ...
}

Just replace MutableLiveData
private val promoIdLiveData: MutableLiveData<String?> = MutableLiveData()
with SingleLiveEvent
private val promoIdLiveData: SingleLiveEvent<String?> = SingleLiveEvent()
The SingleLiveEvent
class SingleLiveEvent<T> : MutableLiveData<T>() {
private val pending = AtomicBoolean(false)
#MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observe(
owner,
Observer { t ->
if (pending.compareAndSet(true, false)) {
observer.onChanged(t)
}
}
)
}
#MainThread
override fun setValue(t: T?) {
pending.set(true)
super.setValue(t)
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
#MainThread
fun call() {
value = null
}
companion object {
private val TAG = "SingleLiveEvent"
}
}

Related

Android - kotlin.UninitializedPropertyAccessException: lateinit property homeActivityBinding has not been initialized

I have a BaseFragment class that is extended by all fragments in my app. And i have a HomeActivity class is a starting activity and also has some generic functionality in it.
Here is my HomeActivity code:
class HomeActivity : HomeActivityContract.View {
private val presenter: HomeActivityContract.Presenter by inject {
parametersOf(Schedulers.computation())
}
override val selectedWatchList: WatchlistItem
get() = homeActivityBinding.watchlistSpinner.selectedWatchList
override val watchlistItems: MutableList<WatchlistItem>
get() = homeActivityBinding.watchlistSpinner.watchlistItems
val bag = CompositeDisposable()
lateinit var homeActivityBinding: ActivityHomeBinding
override
fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (presenter.hasAccount) {
homeActivityBinding = ActivityHomeBinding.inflate(layoutInflater)
setContentView(homeActivityBinding.root)
Observables.combineLatest(
realtimeDatabaseService.enableMaintenancePageForPhone,
realtimeDatabaseService.maintenanceDate
) { isEnabled, _mMsg ->
if (isEnabled) {
homeActivityBinding.homeMaintenanceView.root.visibility = View.VISIBLE
homeActivityBinding.homeParentView.visibility = View.GONE
homeActivityBinding.homeMaintenanceView.maintenanceTV.text = getString(R.string.maintenance_msg, _mMsg)
} else {
homeActivityBinding.homeMaintenanceView.root.visibility = View.GONE
homeActivityBinding.homeParentView.visibility = View.VISIBLE
presenter.attachView(this)
presenter.dataProvider.getAccountUpdate()
.subscribeBy(onError = { it.printStackTrace() }, onNext = {
presenter.sendDeviceIdForCurrentAccount(isNotificationsEnabledInSettings)
}).disposeBy(lifecycle.disposers.onDestroy)
setSupportActionBar(homeActivityBinding.homeToolbar)
presenter.start()
}
}
.subscribe()
.disposeBy(lifecycle.disposers.onDestroy)
} else {
val intent = Intent(this, LoginActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
}
startActivity(intent)
finish()
}
}
fun hideNavigationMenu() {
homeActivityBinding.homeNavigationView.visibility = View.GONE /// This is where it says that homeActivityBinding is uninitialised.
}
BaseFragment code:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (isAdded)
parentFragmentManager.let { fragmentManager ->
takeIf { fragmentManager.backStackEntryCount >= 1 }?.let {
(activity as? HomeActivity)?.hideNavigationMenu()
}
}
}
So BaseFragment is calling hideNavigationMenu in HomeActivity.
Here is the crash report:
Caused by kotlin.UninitializedPropertyAccessException: lateinit property homeActivityBinding has not been initialized
at com.app.android.traderpro.etx.activities.homeActivity.HomeActivity.G5(HomeActivity.kt:7)
at com.app.android.traderpro.etx.activities.homeActivity.HomeActivity.hideNavigationMenu(HomeActivity.kt:404)
at com.app.android.traderpro.etx.fragments.BaseFragment.hideNavigationMenu(BaseFragment.kt:138)
at com.app.android.traderpro.etx.fragments.BaseFragment.onCreate(BaseFragment.kt:153)
at androidx.fragment.app.Fragment.performCreate(Fragment.java:3090)
at androidx.fragment.app.FragmentStateManager.create(FragmentStateManager.java:475)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:257)
at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:113)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1424)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2968)
at androidx.fragment.app.FragmentManager.dispatchCreate(FragmentManager.java:2875)
at androidx.fragment.app.FragmentController.dispatchCreate(FragmentController.java:252)
at androidx.fragment.app.FragmentActivity.onCreate(FragmentActivity.java:220)
at com.app.android.traderpro.etx.activities.BaseActivity.onCreate(BaseActivity.kt:61)
at com.app.android.traderpro.etx.activities.homeActivity.HomeActivity.onCreate(HomeActivity.kt:134)
at android.app.Activity.performCreate(Activity.java:8207)
at android.app.Activity.performCreate(Activity.java:8191)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3819)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4022)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2336)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:246)
at android.app.ActivityThread.main(ActivityThread.java:8653)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)
The first thing is I cannot replicate this crash and there are around 200 crashes reported in Firebase Crashlytics.
And
HomeActivity is the first activity that launches all other fragments. This crash is not happening as soon as this activity is started, it is happening after some time. So I don't understand how homeActivityBinding can be uninitialized.
I'd appreciate it if anyone can tell me how a lateinit property that is initialised can be uninitialized again
Fragment.onCreate() is too early to be trying to access members of the Activity, even though it is already attached. From the onCreate() documentation:
Note that this can be called while the fragment's activity is still in the process of being created. As such, you can not rely on things like the activity's content view hierarchy being initialized at this point.
You should move your code from onCreate to onViewCreated.

Cannot create an instance of class ViewModel unable to find cause

whenever I click on the title, the app Crashes shows this logcat
I am new to Android
using
dagger hilt,
exoplayer,
this is where i touch got error:
Songfragment
#AndroidEntryPoint
class SongFragments : Fragment(R.layout.fragment_song) {
#Inject
lateinit var glide: RequestManager
private lateinit var mainViewModel: MainViewModel
private val songViewModel: SongViewModel by viewModels()
private var curplayingSong: sound? = null
private var playbackState: PlaybackStateCompat? = null
private var shouldUpdateSeekbar = true
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mainViewModel = ViewModelProvider(requireActivity()).get(MainViewModel::class.java)
subscribeToObservers()
ivPlayPauseDetail.setOnClickListener {
curplayingSong?.let {
mainViewModel.playOrToggleSound(it, true)
}
}
seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
if (fromUser) {
setCurPlayerTimeToTextView(progress.toLong())
}
}
override fun onStartTrackingTouch(seekBar: SeekBar?) {
shouldUpdateSeekbar = false
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
seekBar?.let {
mainViewModel.seekTo(it.progress.toLong())
shouldUpdateSeekbar = true
}
}
})
ivSkipPrevious.setOnClickListener {
mainViewModel.skipToPreviousSound()
}
ivSkip.setOnClickListener{
mainViewModel.skipToNextSound()
}
}
private fun updateTitleAndSongImage(sound: sound) {
val title = "${sound.title} - ${sound.subtitle}"
tvSongName.text = title
glide.load(sound.imageUrl).into(ivSongImage)
}
private fun subscribeToObservers() {
mainViewModel.mediaItems.observe(viewLifecycleOwner){
it?.let { result ->
when(result.status){
Status.SUCCESS -> {
result.data?.let { sounds ->
if (curplayingSong == null && sounds.isNotEmpty()) {
curplayingSong = sounds[0]
updateTitleAndSongImage(sounds[0])
}
}
}
else -> Unit
}
}
}
mainViewModel.curPlayingSound.observe(viewLifecycleOwner) {
if(it == null) return#observe
curplayingSong = it.toSong()
updateTitleAndSongImage(curplayingSong!!)
}
mainViewModel.playbackState.observe(viewLifecycleOwner) {
playbackState = it
ivPlayPauseDetail.setImageResource(
if (playbackState?.isPlaying == true) R.drawable.ic_pause else R.drawable.ic_play
)
seekBar.progress = it?.position?.toInt() ?: 0
}
songViewModel.curPlayerPosition.observe(viewLifecycleOwner) {
if (shouldUpdateSeekbar) {
seekBar.progress = it.toInt()
setCurPlayerTimeToTextView(it)
}
}
songViewModel.curSongDuration.observe(viewLifecycleOwner) {
seekBar.max = it.toInt()
val dateFormat = SimpleDateFormat("mm:ss", Locale.getDefault())
tvSongDuration.text = dateFormat.format(it)
}
}
private fun setCurPlayerTimeToTextView(ms: Long) {
val dateFormat = SimpleDateFormat("mm:ss", Locale.getDefault())
tvCurTime.text = dateFormat.format(ms)
}
}
SongviewModel
class SongViewModel #ViewModelInject constructor(
musicServiceConnection: MusicServiceConnection
) : ViewModel() {
private val playbackState = musicServiceConnection.playBackState
private val _curSongDuration = MutableLiveData<Long>()
val curSongDuration: LiveData<Long> = _curSongDuration
private val _curPlayerPosition = MutableLiveData<Long>()
val curPlayerPosition: LiveData<Long> = _curPlayerPosition
init {
updateCurrentplayerPostion()
}
#SuppressLint("NullSafeMutableLiveData")
private fun updateCurrentplayerPostion() {
viewModelScope.launch {
while(true) {
val pos = playbackState.value?.currentPlaybackPosition
if(curPlayerPosition.value != pos){
_curPlayerPosition.postValue(pos)
_curSongDuration.postValue(MusicService.curSoundDuration)
}
delay(UPDATE_PLAYER_POSITION_INTERVAL)
}
}
}
}
logcat:
2022-08-12 19:11:39.122 10888-10983/com.fridayhouse.snoozz E/ion: ioctl c0044901 failed with code -1: Invalid argument
2022-08-12 19:11:39.318 10888-10888/com.fridayhouse.snoozz E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.fridayhouse.snoozz, PID: 10888
java.lang.RuntimeException: Cannot create an instance of class com.fridayhouse.snoozz.ui.viewmodels.SongViewModel
at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.kt:204)
at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.kt:322)
at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.kt:304)
at androidx.lifecycle.SavedStateViewModelFactory.create(SavedStateViewModelFactory.kt:175)
at androidx.lifecycle.SavedStateViewModelFactory.create(SavedStateViewModelFactory.kt:203)
at dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.create(HiltViewModelFactory.java:111)
at androidx.lifecycle.ViewModelProvider$Factory.create(ViewModelProvider.kt:83)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:187)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:153)
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:53)
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:35)
at com.fridayhouse.snoozz.ui.fragments.SongFragments.getSongViewModel(SongFragments.kt:33)
at com.fridayhouse.snoozz.ui.fragments.SongFragments.subscribeToObservers(SongFragments.kt:121)
at com.fridayhouse.snoozz.ui.fragments.SongFragments.onViewCreated(SongFragments.kt:45)
at androidx.fragment.app.Fragment.performViewCreated(Fragment.java:3128)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:552)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:261)
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1890)
at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1814)
at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1751)
at androidx.fragment.app.FragmentManager$5.run(FragmentManager.java:538)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:233)
at android.os.Looper.loop(Looper.java:334)
at android.app.ActivityThread.main(ActivityThread.java:8396)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:582)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1068)
Caused by: java.lang.InstantiationException: java.lang.Class<com.fridayhouse.snoozz.ui.viewmodels.SongViewModel> has no zero argument constructor
at java.lang.Class.newInstance(Native Method)
at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.kt:202)
at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.kt:322) 
at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.kt:304) 
at androidx.lifecycle.SavedStateViewModelFactory.create(SavedStateViewModelFactory.kt:175) 
at androidx.lifecycle.SavedStateViewModelFactory.create(SavedStateViewModelFactory.kt:203) 
at dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.create(HiltViewModelFactory.java:111) 
at androidx.lifecycle.ViewModelProvider$Factory.create(ViewModelProvider.kt:83) 
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:187) 
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:153) 
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:53) 
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:35) 
at com.fridayhouse.snoozz.ui.fragments.SongFragments.getSongViewModel(SongFragments.kt:33) 
at com.fridayhouse.snoozz.ui.fragments.SongFragments.subscribeToObservers(SongFragments.kt:121) 
at com.fridayhouse.snoozz.ui.fragments.SongFragments.onViewCreated(SongFragments.kt:45) 
at androidx.fragment.app.Fragment.performViewCreated(Fragment.java:3128) 
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:552) 
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:261) 
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1890) 
at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1814) 
at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1751) 
at androidx.fragment.app.FragmentManager$5.run(FragmentManager.java:538) 
at android.os.Handler.handleCallback(Handler.java:938) 
at android.os.Handler.dispatchMessage(Handler.java:99) 
at android.os.Looper.loopOnce(Looper.java:233) 
at android.os.Looper.loop(Looper.java:334) 
at android.app.ActivityThread.main(ActivityThread.java:8396) 
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:582) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1068) 
#ViewModelInject has been deprecated. You can check here for more info.
In newer versions, You need to annotate viewmodel class with #AndroidViewModel and constructor with #Inject.
#AndroidViewModel
class SongViewModel #Inject constructor(
musicServiceConnection: MusicServiceConnection
) : ViewModel()
Hope it would solve this issue.
Since you are using Hilt, you shouldn't be using by viewModels(). You should set up your MusicServiceConnection in your Provider, and instead of using private val songViewModel: SongViewModel by viewModels(), use #Inject private lateinit var songViewModel: SongViewModel.
I'm not very familiar with Hilt, so I can't provide more guidance. The below is an explanation of what's going wrong with by viewModels(), and how you would resolve this if you were not using a dependency injection framework.
When you create a ViewModel in your Activity or Fragment, you can supply a factory that is responsible for creating instances of the ViewModel. If you don't supply a factory, then the default factory is used.
The default factory is only capable of creating instances of your ViewModel if your ViewModel constructor's arguments are one of the following:
constructor() Empty constructor (no arguments)
constructor(savedStateHandle: SavedStateHandle)
constructor(application: Application)
constructor(application: Application, savedStateHandle: SavedStateHandle)
I don't think this is documented very well. I had to look at the Jetpack source code to learn it.
Since your ViewModel needs a MusicServiceConnection parameter, you cannot use the default factory. You will need to supply an explicit ViewModelProvider.Factory to the by viewModels() as an argument.
This training has an example of creating a ViewModelProvider.Factory class for a ViewModel that needs a special argument for the constructor. In your case, the solution might look like this:
class SongViewModelFactory(private val musicServiceConnection: MusicServiceConnection) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
require(modelClass.isAssignableFrom(SongViewModel::class.java)) { "Unknown ViewModel class" }
#Suppress("UNCHECKED_CAST")
return SongViewModel(musicServiceConnection) as T
}
}
I don't know where your MusicServiceConnection class comes from, but let's pretend you can simply instantiate it one time for your Activity with an empty constructor. Then you would replace
private val songViewModel: SongViewModel by viewModels()
with
private val songViewModel: SongViewModel by viewModels {
SongViewModelFactory(MusicServiceConnection())
}
Notice that you are passing a lambda function to viewModels instead of directly passing an instance of your factory. This is because the factory is lazily created only one time, even if your Activity is recreated multiple times (like if the user rotates the screen back and forth). The code inside this lambda is only called once when the app navigates to this screen, even if the Activity is recreated for screen rotations or other configuration changes.

Doesn't understand why I get this error when I want use my variable error : lateinit property _mails has not been initialized in Kotlin

I want use my variable _mails that I lateninit in the init in my ViewModel. I can't give the the dataBase access:( I'm trying to get setAdaptater for my AutoCompleteView but when I trying to get the mail by my viewModel it tell me that I have not initialized the variable _mails.
class ConnectionViewModel(
val database: ConnectionDataBaseDao,
application: Application) : AndroidViewModel(application){
private lateinit var _mails : List<Connection>
init {
viewModelScope.launch(Dispatchers.IO) {
_mails = database.getAllConnections()
}
fun getAllConnections():List<Connection> {
return _mails
}
}
It is in the ConnectionFragment that I call the getAllConnections().
class ConnectionFragment : Fragment() {
private lateinit var viewModel: ConnectionViewModel
private lateinit var viewModelFactory: ConnectionViewModelFactory
private lateinit var binding: FragmentConnectionBinding
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(inflater,
R.layout.fragment_connection,container,false)
val application = requireNotNull(this.activity).application
val dataSource = ConnectionDataBase.getInstance(application).connectionDataBaseDao
viewModelFactory = ConnectionViewModelFactory(dataSource, application)
viewModel = ViewModelProvider(this,viewModelFactory).get(ConnectionViewModel::class.java)
binding.connectionViewModel = viewModel
binding.lifecycleOwner = viewLifecycleOwner
return binding.root
}
override fun onStart() {
super.onStart()
var mails = ArrayList<String>()
viewModel.getAllConnections().forEach { mails.add(it.mailId) }
val adapter = ArrayAdapter<String>(this.requireContext(),android.R.layout.simple_list_item_1,mails)
binding.mail.setAdapter(adapter)
}
}
There is the log error
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.soccerinfo, PID: 9206
kotlin.UninitializedPropertyAccessException: lateinit property _mails has not been initialized
at com.example.soccerinfo.connection.ConnectionViewModel.getAllConnections(ConnectionViewModel.kt:65)
at com.example.soccerinfo.connection.ConnectionFragment.onStart(ConnectionFragment.kt:118)
at androidx.fragment.app.Fragment.performStart(Fragment.java:2731)
at androidx.fragment.app.FragmentStateManager.start(FragmentStateManager.java:365)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1206)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1368)
at androidx.fragment.app.FragmentManager.moveFragmentToExpectedState(FragmentManager.java:1446)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1509)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2637)
at androidx.fragment.app.FragmentManager.dispatchStart(FragmentManager.java:2595)
at androidx.fragment.app.Fragment.performStart(Fragment.java:2740)
at androidx.fragment.app.FragmentStateManager.start(FragmentStateManager.java:365)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1206)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1368)
at androidx.fragment.app.FragmentManager.moveFragmentToExpectedState(FragmentManager.java:1446)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1509)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2637)
at androidx.fragment.app.FragmentManager.dispatchStart(FragmentManager.java:2595)
at androidx.fragment.app.FragmentController.dispatchStart(FragmentController.java:258)
at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:550)
at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:210)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1391)
at android.app.Activity.performStart(Activity.java:7157)
at android.app.ActivityThread.handleStartActivity(ActivityThread.java:2937)
at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:180)
at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:165)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:142)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:70)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
Database access is an async operation, so it may take some time to complete. But you are trying to access the _mails property in onStart using viewModel.getAllConnections().forEach { mails.add(it.mailId) }. If database operation is not yet complete then _mails property is not initilized and you get the error
There are many ways to solve this issue.
Make database method return LiveData
if you are using Room database then you can make database.getAllConnections() return LiveData<List<Connection>> and then you can observe this in your fragment as
viewModel.getAllConnections().observe(this, Observer{
// here you receive the result of database.getAllConnections
}
// viewModel.getAllConnections() = database.getAllConnections
Change type of _mails to MutableLiveData<List<Connection>>
class ConnectionViewModel(
val database: ConnectionDataBaseDao,
application: Application) : AndroidViewModel(application){
val mails : MutableLiveData<List<Connection>> = MutableLiveData()
init {
viewModelScope.launch(Dispatchers.IO) {
_mails.postValue(database.getAllConnections())
}
}
And now you can observe _mails in your Fragment as
viewModel.mails.observe(this, Observer{
// here you receive the result of database.getAllConnections
}

: kotlin.UninitializedPropertyAccessException: lateinit property manager has not been initialized

I am developing news and I am getting following nullpointexception
java.lang.RuntimeException: Unable to start activity ComponentInfo{yodgorbek.komilov.musobaqayangiliklari/yodgorbek.komilov.musobaqayangiliklari.ui.WelcomeActivity}: kotlin.UninitializedPropertyAccessException: lateinit property manager has not been initialized
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2976)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3113)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:113)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:71)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1858)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:201)
at android.app.ActivityThread.main(ActivityThread.java:6820)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:922)
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property manager has not been initialized
at yodgorbek.komilov.musobaqayangiliklari.ui.WelcomeActivity.onCreate(WelcomeActivity.kt:26)
at android.app.Activity.performCreate(Activity.java:7224)
at android.app.Activity.performCreate(Activity.java:7213)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1272)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2956)
... 11 more
below my WelcomeActivity.kt class
class WelcomeActivity : AppIntro() {
private lateinit var manager: PreferencesManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Make sure you don't call setContentView!
if (manager.isFirstRun()) {
showIntroSlides()
} else {
goToMain()
}
}
// Call addSlide passing your Fragments.
// You can use AppIntroFragment to use a pre-built fragment
private fun showIntroSlides() {
manager.setFirstRun()
addSlide(
AppIntroFragment.newInstance(
title = "Welcome to the NewsApp",
description = "NewsApp give your information about life news around the world",
imageDrawable = R.drawable.news,
backgroundDrawable = R.drawable.news_slider,
titleColor = Color.YELLOW,
descriptionColor = Color.RED,
backgroundColor = Color.BLUE,
titleTypefaceFontRes = R.font.opensans_light,
descriptionTypefaceFontRes = R.font.opensans_regular
)
)
addSlide(
AppIntroFragment.newInstance(
title = "...Let's get started!",
description = "This is the last slide, I won't annoy you more :)"
)
)
}
private fun goToMain() {
startActivity(Intent(this, MainActivity::class.java))
}
override fun onSkipPressed(currentFragment: Fragment?) {
super.onSkipPressed(currentFragment)
goToMain()
}
override fun onDonePressed(currentFragment: Fragment?) {
super.onDonePressed(currentFragment)
goToMain()
}
override fun onSlideChanged(oldFragment: Fragment?, newFragment: Fragment?) {
super.onSlideChanged(oldFragment, newFragment)
Log.d("Hello", "Changed")
}
}
I don't understand what is the causing null pointer exception even I have tried initialize manager following way
private var manager: PreferencesManager? = null
but it did not solve my problem
I want to know where I am making mistake what I have to do avoid nullpointer exception
It's simple by looking into logs. Your manager is not initialized before using. Look at onCreate
private lateinit var manager: PreferencesManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Make sure you don't call setContentView!
// <<<<<<<<<<<<<<< INITIALISE manager HERE >>>>>>>>>>>>>>
if (manager.isFirstRun()) { <<<<<<<< HERE you are using an uninitilised manager
showIntroSlides()
} else {
goToMain()
}
}
Initialise manager before if (manager.isFirstRun()) { in onCreate()

Unit testing LiveData observerForever results in NullPointer Exception with Junit5

I am using Android databinding to listen to live data changes and I would like to observe changes on the viewmodel level (Rather then observing on fragment and then sending a callback to the viewmodel)
The observerForever is interesting as it serves the purpose for me. However when I run a test I get the following error:
java.lang.NullPointerException
at androidx.arch.core.executor.DefaultTaskExecutor.isMainThread(DefaultTaskExecutor.java:77)
at androidx.arch.core.executor.ArchTaskExecutor.isMainThread(ArchTaskExecutor.java:116)
at androidx.lifecycle.LiveData.assertMainThread(LiveData.java:461)
at androidx.lifecycle.LiveData.observeForever(LiveData.java:222)
at com.bcgdv.ber.maha.login.ui.LoginViewModel.<init>(LoginViewModel.kt:43)
at com.bcgdv.ber.maha.login.ui.LoginViewModelTest.<init>(LoginViewModelTest.kt:26)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.junit.platform.commons.util.ReflectionUtils.newInstance(ReflectionUtils.java:443)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:60)
My code is as follows in the viewmodel class:
val observerEmail: Observer<String> = Observer {
setEmailError(it)
checkLoginButton()
}
var email = MutableLiveData<String>()
init {
email.observeForever(observerEmail)
}
Also to note is I am using Junit5.
#ExtendWith(InstantTaskExecutorExtension::class)
class LoginViewModelTest {
val emailAddress = "xyz#xyz.com"
val password = "password"
val user: User = User("1", "xyz#xyz.com", "password")
val loginUsecase: LoginUseCase = mock {
on { loginUser(emailAddress, password) } doReturn (Single.just(user))
}
private val loginViewModel: LoginViewModel = LoginViewModel(
loginUsecase,
LoginCredentialsValidator(),
Schedulers.trampoline(),
Schedulers.trampoline()
)
#Test
fun should_return_user_as_null_initially() {
whenever(loginUsecase.getUser()).thenReturn(null)
loginViewModel.init()
assertEquals(
expected = null,
actual = loginViewModel.obsEmail.get()
)
}}
And this is the InstantTaskExecutorExtension.
class InstantTaskExecutorExtension : BeforeEachCallback, AfterEachCallback {
override fun beforeEach(context: ExtensionContext?) {
ArchTaskExecutor.getInstance()
.setDelegate(object : TaskExecutor() {
override fun executeOnDiskIO(runnable: Runnable) = runnable.run()
override fun postToMainThread(runnable: Runnable) = runnable.run()
override fun isMainThread(): Boolean = true
})
}
override fun afterEach(context: ExtensionContext?) {
ArchTaskExecutor.getInstance().setDelegate(null)
}
}
In general it's recommended to use LiveData only for View Model <-> View communication, however I think the issue is:
private val loginViewModel: LoginViewModel = LoginViewModel(
...
)
Because since this is a member variable it would be executed before the test and it's already implicitly executing init() since you call the constructor.
No need to call init() explicitly. I'd remove the loginViewModel member variable and instantiate it in the test function via the constructor:
#Test
fun should_return_user_as_null_initially() {
...
LoginViewModel(
...
)
...
}

Categories

Resources