I've got two fragments in viewpager and in every fragment is with seperated recyclerViews.
When I initialize seperated viewmodels like:
first fragment
override val vm: NotesManagerViewModel by viewModels()
second fragment
override val vm: SetGoalsViewModel by viewModels()
view pager
override val vm: ViewPagerViewModel by viewModels()
I've got error:
Process: com.example.learningmanager, PID: 10369
java.lang.RuntimeException: Cannot create an instance of class com.example.learningmanager.fragments.setgoals.ui.SetGoalsViewModel
When I use in my two fragments
by activityViewModels
to initialize vm's recyclerViews it doesn't crash, but adapters doesn't work properly it doesn't refresh well or doesn't load data at all.
I understand it is because it lives for activity scope and doesn't refresh data at all, doesn't create new viewmodel instance.
... but how to handle that kind of implementation to refresh my viewmodels, data every time I need it i.e. after adding new element to database and in adapter.
Full error stacktrace:
W/System: A resource failed to call release.
D/AndroidRuntime: Shutting down VM
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.learningmanager, PID: 10369
java.lang.RuntimeException: Cannot create an instance of class com.example.learningmanager.fragments.setgoals.ui.SetGoalsViewModel
at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:221)
at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.java:278)
at androidx.lifecycle.SavedStateViewModelFactory.create(SavedStateViewModelFactory.java:112)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:185)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150)
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelProvider.kt:54)
at androidx.lifecycle.ViewModelLazy.getValue(ViewModelProvider.kt:41)
at com.example.learningmanager.fragments.setgoals.ui.SetGoalsFragment.getVm(SetGoalsFragment.kt:21)
at com.example.learningmanager.fragments.setgoals.ui.SetGoalsFragment.getVm(SetGoalsFragment.kt:18)
at com.example.learningmanager.base.ui.BaseFragment.collectNavigationEvents(BaseFragment.kt:42)
at com.example.learningmanager.base.ui.BaseFragment.onViewCreated(BaseFragment.kt:36)
at com.example.learningmanager.fragments.setgoals.ui.SetGoalsFragment.onViewCreated(SetGoalsFragment.kt:25)
at androidx.fragment.app.Fragment.performViewCreated(Fragment.java:3019)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:551)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:261)
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1840)
at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1758)
at androidx.fragment.app.FragmentManager.execSingleAction(FragmentManager.java:1670)
at androidx.fragment.app.BackStackRecord.commitNow(BackStackRecord.java:317)
at androidx.viewpager2.adapter.FragmentStateAdapter.placeFragmentInViewHolder(FragmentStateAdapter.java:341)
at androidx.viewpager2.adapter.FragmentStateAdapter.onViewAttachedToWindow(FragmentStateAdapter.java:276)
at androidx.viewpager2.adapter.FragmentStateAdapter.onViewAttachedToWindow(FragmentStateAdapter.java:67)
at androidx.recyclerview.widget.RecyclerView.dispatchChildAttached(RecyclerView.java:7556)
at androidx.recyclerview.widget.RecyclerView$5.addView(RecyclerView.java:860)
at androidx.recyclerview.widget.ChildHelper.addView(ChildHelper.java:107)
at androidx.recyclerview.widget.RecyclerView$LayoutManager.addViewInt(RecyclerView.java:8601)
at androidx.recyclerview.widget.RecyclerView$LayoutManager.addView(RecyclerView.java:8559)
at androidx.recyclerview.widget.RecyclerView$LayoutManager.addView(RecyclerView.java:8547)
at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1641)
at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1587)
at androidx.recyclerview.widget.LinearLayoutManager.scrollBy(LinearLayoutManager.java:1391)
at androidx.recyclerview.widget.LinearLayoutManager.scrollHorizontallyBy(LinearLayoutManager.java:1116)
at androidx.recyclerview.widget.RecyclerView.scrollStep(RecyclerView.java:1838)
at androidx.recyclerview.widget.RecyclerView$SmoothScroller.onAnimation(RecyclerView.java:11920)
at androidx.recyclerview.widget.RecyclerView$ViewFlinger.run(RecyclerView.java:5390)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:972)
at android.view.Choreographer.doCallbacks(Choreographer.java:796)
at android.view.Choreographer.doFrame(Choreographer.java:727)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:957)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
E/AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
Caused by: java.lang.InstantiationException: java.lang.Class<com.example.learningmanager.fragments.setgoals.ui.SetGoalsViewModel> has no zero argument constructor
at java.lang.Class.newInstance(Native Method)
at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:219)
... 45 more
EDIT:
viewmodel second
#HiltViewModel
class SetGoalsViewModel #Inject constructor(
private val getGoalsItemDetailsUseCase: GetGoalsItemDetailsUseCase
) : BaseViewModel() {
val setGoalsData = MutableStateFlow<List<SetGoalsData>>(emptyList())
init {
getActualState()
}
private fun getActualState() {
viewModelScope.launch {
getGoalsItemDetailsUseCase.build(Unit).collect {
setGoalsData.value = it
}
}
}
fun onNavigateToSave() {
navigateTo(SetGoalsToSaveScreenKey())
}
fun deleteItem(id: Int) {
viewModelScope.launch {
//usecase with id
getActualState()
}
}
fun onItemClicked(id: Int) {
// navigaton to root
}
}
viewmodel first
#HiltViewModel
class NotesManagerViewModel #Inject constructor(
private val application: Application,
private val addNotesItemsUseCase: AddNotesItemsUseCase,
private val deleteNotesItemsUseCase: DeleteNotesItemsUseCase,
private val getNotesItemsUseCase: GetNotesItemsUseCase,
private val searchNotesItemUseCase: SearchNotesItemUseCase,
private val updateNotesItemsUseCase: UpdateNotesItemsUseCase
) : BaseViewModel() {
val noteDataList = MutableStateFlow<List<NoteData>>(emptyList())
val searchDataList = MutableStateFlow<List<NoteData>>(emptyList())
init {
getActualState()
}
fun getActualState() {
viewModelScope.launch {
getNotesItemsUseCase.build(Unit).collect {
noteDataList.value = it
Log.d("newNoteData", "${noteDataList.value}")
}
}
}
fun updateNote(existingNoteData: NoteData) {
viewModelScope.launch {
updateNotesItemsUseCase.create(existingNoteData)
getActualState()
}
}
fun deleteNote(existingNoteData: NoteData) {
viewModelScope.launch {
deleteNotesItemsUseCase.create(existingNoteData)
getActualState()
}
}
fun saveNote(newNoteData: NoteDataDetailsResponse) {
viewModelScope.launch {
addNotesItemsUseCase.create(newNoteData)
Log.d("newNoteData", "$newNoteData")
}
}
fun searchNote(query: String) {
viewModelScope.launch {
searchNotesItemUseCase.create(query).collectLatest {
searchDataList.value = it
Log.d("FromVM", "${searchDataList.value}")
}
}
}
}
Related
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.
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.
In this demo app, I used a retrofit, Moshi, MVVM, dagger hilt, and kotlin coroutine, I got stuck in this exception, I tried to change the structure of suspended fun in PokemonApiService delete it, change the return type from Flow<PokemonResponse> to Flow<Response<PokemonResponse>> but all this didn't work
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.mml.pokemonkotlin, PID: 3282
java.lang.IllegalArgumentException: Unable to create converter for kotlinx.coroutines.flow.Flow<com.mml.pokemonkotlin.model.PokemonResponse>
for method PokemonApiService.getPokemons
at retrofit2.Utils.methodError(Utils.java:54)
at retrofit2.HttpServiceMethod.createResponseConverter(HttpServiceMethod.java:126)
at retrofit2.HttpServiceMethod.parseAnnotations(HttpServiceMethod.java:85)
at retrofit2.ServiceMethod.parseAnnotations(ServiceMethod.java:39)
at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:202)
at retrofit2.Retrofit$1.invoke(Retrofit.java:160)
at java.lang.reflect.Proxy.invoke(Proxy.java:1006)
at $Proxy1.getPokemons(Unknown Source)
at com.mml.pokemonkotlin.data.RemoteDataSource.getPokemons(RemoteDataSource.kt:24)
at com.mml.pokemonkotlin.viewmodels.PokemonViewModel$getPokemonList$1.invokeSuspend(PokemonViewModel.kt:26)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:367)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:25)
at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:110)
at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:126)
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:47)
at kotlinx.coroutines.BuildersKt.launch$default(Unknown Source:1)
at com.mml.pokemonkotlin.viewmodels.PokemonViewModel.getPokemonList(PokemonViewModel.kt:25)
at com.mml.pokemonkotlin.MainActivity.onCreate(MainActivity.kt:33)
at android.app.Activity.performCreate(Activity.java:7994)
at android.app.Activity.performCreate(Activity.java:7978)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3422)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601)
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:2066)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
Caused by: java.lang.IllegalArgumentException: No JsonAdapter for kotlinx.coroutines.flow.Flow<com.mml.pokemonkotlin.model.PokemonResponse> (with no annotations)
at com.squareup.moshi.Moshi.adapter(Moshi.java:155)
at com.squareup.moshi.Moshi.adapter(Moshi.java:105)
at retrofit2.converter.moshi.MoshiConverterFactory.responseBodyConverter(MoshiConverterFactory.java:89)
at retrofit2.Retrofit.nextResponseBodyConverter(Retrofit.java:362)
at retrofit2.Retrofit.responseBodyConverter(Retrofit.java:345)
at retrofit2.HttpServiceMethod.createResponseConverter(HttpServiceMethod.java:124)
at retrofit2.HttpServiceMethod.parseAnnotations(HttpServiceMethod.java:85)
at retrofit2.ServiceMethod.parseAnnotations(ServiceMethod.java:39)
at retrofit2.Retrofit.loadServiceMethod(Retrofit.java:202)
at retrofit2.Retrofit$1.invoke(Retrofit.java:160)
at java.lang.reflect.Proxy.invoke(Proxy.java:1006)
at $Proxy1.getPokemons(Unknown Source)
at com.mml.pokemonkotlin.data.RemoteDataSource.getPokemons(RemoteDataSource.kt:24)
at com.mml.pokemonkotlin.viewmodels.PokemonViewModel$getPokemonList$1.invokeSuspend(PokemonViewModel.kt:26)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:367)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30)
at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable$default(Cancellable.kt:25)
at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:110)
at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:126)
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:47)
at kotlinx.coroutines.BuildersKt.launch$default(Unknown Source:1)
at com.mml.pokemonkotlin.viewmodels.PokemonViewModel.getPokemonList(PokemonViewModel.kt:25)
at com.mml.pokemonkotlin.MainActivity.onCreate(MainActivity.kt:33)
at android.app.Activity.performCreate(Activity.java:7994)
at android.app.Activity.performCreate(Activity.java:7978)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3422)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601)
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:2066)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
BaseAppliction class
#HiltAndroidApp
class BaseApplication : Application() {
}
model classes
Pokemon.kt
#JsonClass(generateAdapter = true)
data class Pokemon(
#Json(name = "name")
var name: String,
#Json(name = "url")
var url: String
)
Pokemon response
#JsonClass(generateAdapter = true)
data class PokemonResponse(
#Json(name = "count")
var count: Int,
#Json(name = "next")
var next: String,
#Json(name = "previous")
var previous: String?,
#Json(name = "results")
var results: ArrayList<Pokemon>
)
PokemonApiService interface
interface PokemonApiService {
#GET("pokemon")
suspend fun getPokemons() : Flow<PokemonResponse>
}
Network Module
#InstallIn(SingletonComponent::class)
#Module
object NetworkModule {
#Singleton
#Provides
fun provideHttpClient(): OkHttpClient {
return OkHttpClient.Builder().readTimeout(
15, TimeUnit.SECONDS
).connectTimeout(15, TimeUnit.SECONDS).build()
}
#Singleton
#Provides
fun provideMoshi(): Moshi {
return Moshi.Builder().build()
}
#Singleton
#Provides
fun provideRetrofitInstance(
okHttpClient: OkHttpClient,
): Retrofit {
return Retrofit.Builder()
.baseUrl("https://pokeapi.co/api/v2/")
.client(okHttpClient)
.addConverterFactory(MoshiConverterFactory.create(provideMoshi()))
.build()
}
#Singleton
#Provides
fun provideApiService(retrofit: Retrofit): PokemonApiService {
return retrofit.create(PokemonApiService::class.java)
}
}
RemoteDataSource class
class RemoteDataSource #Inject constructor(private val pokemonApiService: PokemonApiService) {
private val TAG = "RemoteDataSource"
suspend fun getPokemons(): PokemonResponse? {
var response: PokemonResponse? = null
pokemonApiService.getPokemons()
.catch { Log.e(TAG, "getPokemons: ${it.message}") }
.onEmpty { Log.e(TAG, "empty response ") }
.collect {
response = it
}
return response
}
}
PokemonViewModel
private const val TAG = "PokemonViewModel"
#HiltViewModel
class PokemonViewModel #Inject constructor(private val remoteDataSource: RemoteDataSource) :
ViewModel() {
var pokemonsResponse: MutableLiveData<ArrayList<Pokemon>> = MutableLiveData()
fun getPokemonList(){
viewModelScope.launch {
pokemonsResponse.value = remoteDataSource.getPokemons()?.results
}
}
}
pokemon adapter
class PokemonAdapter : RecyclerView.Adapter<PokemonAdapter.PokemonViewHolder>() {
private var pokemonList = emptyList<Pokemon>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PokemonViewHolder {
val binding = PokemonItemBinding.inflate(
LayoutInflater.from(parent.context),
parent, false
)
return PokemonViewHolder(binding)
}
override fun onBindViewHolder(holder: PokemonViewHolder, position: Int) {
val currentPokemon = pokemonList[position]
holder.bind(currentPokemon)
}
fun setList(pokemonList: ArrayList<Pokemon>){
this.pokemonList = pokemonList
notifyDataSetChanged()
}
override fun getItemCount(): Int {
return pokemonList.size
}
inner class PokemonViewHolder(private val binding: PokemonItemBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(pokemon: Pokemon) {
binding.pokemonNameTV.text = pokemon.name
}
}
}
and finally MainActivity.kt
private const val TAG = "MainActivity"
#AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private lateinit var pokemonViewModel: PokemonViewModel
private lateinit var binding: ActivityMainBinding
private lateinit var pokemonAdapter: PokemonAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
pokemonViewModel = ViewModelProvider(this).get(PokemonViewModel::class.java)
pokemonAdapter = PokemonAdapter()
binding.pokemonRecyclerView.adapter = pokemonAdapter
pokemonViewModel.getPokemonList()
pokemonViewModel.pokemonsResponse.observe(this, {
if (it.isNullOrEmpty()) {
Log.e(TAG, "it is null")
} else {
pokemonAdapter.setList(it)
}
})
}
}
As of now I'm not sure if Retrofit suports Flow, I guess it doesn't. You can remove it from the returning type:
interface PokemonApiService {
#GET("pokemon")
suspend fun getPokemons() : PokemonResponse
}
after doing some search I found this answer the solution for the second exception, I add this line to the dependencies
kapt "com.squareup.moshi:moshi-kotlin-codegen:1.13.0"
I also changed the return type of getPokemons fun from Flow to PokemonResponse,
finally, I changed results from ArrayList to List because the Moshi doesn't support it yet
#Json(name = "results")
var results: List<Pokemon>
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
}
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"
}
}