Hey all I am having problems figuring out hilt with android. Ive googled around and I can't find this specific problem. I seem to getting a "View Model has no zero argument constructor". I saw on another post that it was for a missing #AndroidEntryPoint annotation in their main activity however I added that and at this point I am a little stumped.
So far I have an application class
#HiltAndroidApp
class RecipeApplication: Application() {
}
The main activity
#AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = primaryBackgroundColor
) {
RecipeBookApp()
}
}
}
}
}
The View model
#HiltViewModel
class RecipeViewModel #Inject constructor(
private val recipeRepo: RecipeRepo
) : ViewModel() {....}
and then the Composable setting up my UI.
#Composable
fun RecipeBookApp(
navController: NavHostController = rememberNavController()
) {
Log.d("GetRecipe", "Still in Recipe Screen About to make the call")
val recipeViewModel = viewModel(modelClass = RecipeViewModel::class.java)
var editMode by remember { mutableStateOf(false) }
Scaffold(
backgroundColor = primaryBackgroundColor,
topBar = {
val title = "Edit"
Column {
Row(Modifier.padding(10.dp)) {
NiceButton(title = title) {
editMode = !editMode
}
}
}
},
bottomBar = {
}) { innerPadding ->
NavHost(
navController,
RecipeScreen.Start.name
) {
composable(route = RecipeScreen.Start.name) {
RecipeGridScreen(
onGridButtonClick = {
navController.navigate(RecipeScreen.RecipePage.name)
}
)
}
composable(route = RecipeScreen.RecipePage.name) {
val state = recipeViewModel.recipeState.collectAsState()
RecipeView(state.value)
}
}
}
}
RecipeRepo
class RecipeRepo #Inject constructor(
private val recipeApi: RecipeApi
) {
suspend fun getAllRecipes(): RecipeList {
return recipeApi.getRecipes()
}
}
RecipeApi
interface RecipeApi {
#GET("recipe/getRecipes")
suspend fun getRecipes(): RecipeList
}
RecipeApiModule
#Module
#InstallIn(SingletonComponent::class)
object RecipeApiModule {
#Provides
#Singleton
fun provideApi(builder: Retrofit.Builder): RecipeApi {
return builder
.build()
.create(RecipeApi::class.java)
}
#Provides
#Singleton
fun provideRetrofit(): Retrofit.Builder {
return Retrofit.Builder()
.baseUrl(RECIPE_URL)
.addConverterFactory(GsonConverterFactory.create())
}
}
Process: com.bunkware.bunkyrecipe, PID: 6287
java.lang.RuntimeException: Cannot create an instance of class
com.bunkware.bunkyrecipe.ui.recipe.RecipeViewModel
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.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.kt:278)
at
androidx.lifecycle.SavedStateViewModelFactory.create(SavedStateViewModelFactory.kt:128)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:187)
at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:153)
at
androidx.lifecycle.viewmodel.compose.ViewModelKt.get(ViewModel.kt:215)
at
androidx.lifecycle.viewmodel.compose.ViewModelKt.viewModel(ViewModel.kt:156)
at
com.bunkware.bunkyrecipe.ui.RecipeGridScreenKt.RecipeGridScreen(RecipeGridScreen.kt:30)
at
com.bunkware.bunkyrecipe.ui.recipe.RecipeScreenKt$RecipeBookApp$2$1$1.invoke(RecipeScreen.kt:63)
at
com.bunkware.bunkyrecipe.ui.recipe.RecipeScreenKt$RecipeBookApp$2$1$1.invoke(RecipeScreen.kt:62)
at
androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:116)
at
androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at
androidx.navigation.compose.NavHostKt$NavHost$4$2.invoke(NavHost.kt:163)
at
androidx.navigation.compose.NavHostKt$NavHost$4$2.invoke(NavHost.kt:162)
at
androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
at
androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at
androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
at
androidx.compose.runtime.saveable.SaveableStateHolderImpl.SaveableStateProvider(SaveableStateHolder.kt:84)
at
androidx.navigation.compose.NavBackStackEntryProviderKt.SaveableStateProvider(NavBackStackEntryProvider.kt:65)
at
androidx.navigation.compose.NavBackStackEntryProviderKt.access$SaveableStateProvider(NavBackStackEntryProvider.kt:1)
at
androidx.navigation.compose.NavBackStackEntryProviderKt$LocalOwnersProvider$1.invoke(NavBackStackEntryProvider.kt:52)
at
androidx.navigation.compose.NavBackStackEntryProviderKt$LocalOwnersProvider$1.invoke(NavBackStackEntryProvider.kt:51)
at
androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
at
androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at
androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
at
androidx.navigation.compose.NavBackStackEntryProviderKt.LocalOwnersProvider(NavBackStackEntryProvider.kt:47)
at
androidx.navigation.compose.NavHostKt$NavHost$4.invoke(NavHost.kt:162)
at
androidx.navigation.compose.NavHostKt$NavHost$4.invoke(NavHost.kt:141)
at
androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:116)
at
androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at
androidx.compose.animation.CrossfadeKt$Crossfade$5$1.invoke(Crossfade.kt:133)
at
androidx.compose.animation.CrossfadeKt$Crossfade$5$1.invoke(Crossfade.kt:128)
at
androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
at
androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at androidx.compose.animation.CrossfadeKt.Crossfade(Crossfade.kt:142)
at androidx.compose.animation.CrossfadeKt.Crossfade(Crossfade.kt:73)
at androidx.navigation.compose.NavHostKt.NavHost(NavHost.kt:141)
at androidx.navigation.compose.NavHostKt$NavHost$5.invoke(Unknown
Source:13)
at androidx.navigation.compose.NavHostKt$NavHost$5.invoke(Unknown
Source:10)
at
androidx.compose.runtime.RecomposeScopeImpl.compose(RecomposeScopeImpl.kt:145)
2023-02-04 11:45:31.431 6287-6287 AndroidRuntime
com.bunkware.bunkyrecipe E at
androidx.compose.runtime.ComposerImpl.recomposeToGroupEnd(Composer.kt:2375)
at
androidx.compose.runtime.ComposerImpl.skipCurrentGroup(Composer.kt:2643)
at
androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:3260)
at
androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:3238)
at
androidx.compose.runtime.SnapshotStateKt__DerivedStateKt.observeDerivedStateRecalculations(DerivedState.kt:341)
at
androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(Unknown
Source:1)
at androidx.compose.runtime.ComposerImpl.doCompose(Composer.kt:3238)
at
androidx.compose.runtime.ComposerImpl.recompose$runtime_release(Composer.kt:3203)
at
androidx.compose.runtime.CompositionImpl.recompose(Composition.kt:771)
at
androidx.compose.runtime.Recomposer.performRecompose(Recomposer.kt:1031)
at
androidx.compose.runtime.Recomposer.access$performRecompose(Recomposer.kt:125)
at
androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:534)
at
androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:503)
at
androidx.compose.ui.platform.AndroidUiFrameClock$withFrameNanos$2$callback$1.doFrame(AndroidUiFrameClock.android.kt:34)
at
androidx.compose.ui.platform.AndroidUiDispatcher.performFrameDispatch(AndroidUiDispatcher.android.kt:109)
at
androidx.compose.ui.platform.AndroidUiDispatcher.access$performFrameDispatch(AndroidUiDispatcher.android.kt:41)
at
androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.doFrame(AndroidUiDispatcher.android.kt:69)
at
android.view.Choreographer$CallbackRecord.run(Choreographer.java:1229)
at
android.view.Choreographer$CallbackRecord.run(Choreographer.java:1239)
at android.view.Choreographer.doCallbacks(Choreographer.java:899)
at android.view.Choreographer.doFrame(Choreographer.java:827)
at
android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1214)
at android.os.Handler.handleCallback(Handler.java:942)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7872)
at java.lang.reflect.Method.invoke(Native Method)
at
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException:
[androidx.compose.runtime.PausableMonotonicFrameClock#dfc23f4,
androidx.compose.ui.platform.MotionDurationScaleImpl#a07d71d,
StandaloneCoroutine{Cancelling}#64f9d92, AndroidUiDispatcher#2d4c363]
Caused by: java.lang.InstantiationException:
java.lang.Class<com.bunkware.bunkyrecipe.ui.recipe.RecipeViewModel>
has no zero argument constructor
at java.lang.Class.newInstance(Native Method)
at
androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.kt:202)
... 71 more
Your error message says RecipeGridScreen inside your NavHost is calling viewModel(). As per the Hilt and Navigation Compose docs, you must always use hiltViewModel() when inside a NavHost so that Hilt can find the correct factory.
So:
Add the hilt-navigation-compose dependency to your build.gradle file:
implementation("androidx.hilt:hilt-navigation-compose:1.0.0")
Change all calls to viewModel() to hiltViewModel(). This is only required inside your NavHost, but it works everywhere, so you can do a find/replace of all instances if you'd like.
Related
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'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}")
}
}
}
}
I'm currently building an app with Jetpack Compose and Some other Jetpack Libraries,
and I use Room for storing data like this
#Dao
interface ClassDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertClassList(classes: List<ClassData>)
#Query("SELECT * FROM ClassData WHERE id=:id")
fun getClassList(id: String): Flow<List<ClassData>>
}
#Database(
entities = [ClassData::class],
version = 1,
exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun classDao(): ClassDao
}
and I use Repository for remote and local model integration like this
class ResourceRepository
#Inject
constructor(
private val userPreference: UserPreference,
private val classDao: ClassDao
) {
fun getClassList() = classDao.getClassList(userPreference.getCachedUserId()).flowOn(Dispatchers.IO)
}
and I use Hilt for dependency injection like this
#Module
#InstallIn(SingletonComponent::class)
object PersistenceModule {
#Provides
#Singleton
fun provideAppDatabase(application: Application): AppDatabase {
return Room.databaseBuilder(
application, AppDatabase::class.java, application.getString(R.string.database))
.fallbackToDestructiveMigration()
.build()
}
#Provides
#Singleton
fun provideClassDao(appDatabase: AppDatabase): ClassDao {
return appDatabase.classDao()
}
}
#Module
#InstallIn(ViewModelComponent::class)
object RepositoryModule {
#Provides
#ViewModelScoped
fun provideResourceRepository(
apiService: ApiService,
userPreference: UserPreference,
classDao: ClassDao
): ResourceRepository {
return ResourceRepository(
apiService,
userPreference,
classDao)
}
}
then I create Viewmodel for communicate data with Composable
#HiltViewModel
class MainViewModel #Inject constructor(private val resourceRepository: ResourceRepository) : ViewModel() {
private val _toast: MutableLiveData<String> = MutableLiveData("")
val toast: LiveData<String>
get() = _toast
val classList = resourceRepository.getClassList()
}
Then I create my MainActivity layout with Jetpack Compose and JetPack Compose Navigation, using BottomNavigation with NavHost to build a traditional BottomNavigation Activity
#Composable
fun Mobile4Main() {
val viewModel = hiltViewModel<MainViewModel>()
val context = LocalContext.current
LocalLifecycleOwner.current.let { owner ->
viewModel.toast.observe(owner) {
if (it.isNotBlank()) {
ToastUtil.show(context, it)
}
}
}
val navController = rememberNavController()
val scaffoldState = rememberScaffoldState(rememberDrawerState(DrawerValue.Open))
Scaffold(
scaffoldState = scaffoldState,
topBar = { TopAppBar(title = { Text("Home") }) },
floatingActionButtonPosition = FabPosition.End,
floatingActionButton = {
FloatingActionButton(onClick = { viewModel.getResources() }) {
Icon(Icons.Filled.Refresh, "", tint = MaterialTheme.colors.background)
}
},
bottomBar = { MainBottomNavigation(navController) })
{ innerPadding ->
MainNavHost(navController, viewModel, innerPadding)
}
}
#Composable
fun MainBottomNavigation(
navController: NavHostController
) {
BottomNavigation {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
items.forEach { screen ->
BottomNavigationItem(
icon = { Icon(screen.icon, contentDescription = null) },
label = { Text(stringResource(screen.resourceId)) },
selected = currentRoute == screen.route,
onClick = {
if (currentRoute != screen.route) {
navController.navigate(screen.route) {
navController.graph.startDestinationRoute?.let {
popUpTo(it) {
saveState = true
}
}
launchSingleTop = true
restoreState = true
}
}
})
}
}
}
#Composable
fun MainNavHost(
navController: NavHostController,
mainViewModel: MainViewModel,
innerPadding: PaddingValues
) {
NavHost(
navController,
startDestination = Screen.ClassList.route,
Modifier.padding(innerPadding)
) {
composable(Screen.ClassList.route) {
ClassPage(mainViewModel, Modifier.fillMaxHeight())
}
composable(Screen.ExamList.route) {
ExamPage(mainViewModel, Modifier.fillMaxHeight())
}
composable(Screen.ScoreList.route) {
ScorePage(mainViewModel, Modifier.fillMaxHeight())
}
composable(Screen.Statistics.route) {
StatisticsPage(mainViewModel, Modifier.fillMaxHeight())
}
}
}
sealed class Screen(val route: String, #StringRes val resourceId: Int, val icon: ImageVector) {
object ClassList :
Screen("classList", R.string.class_bottom_navigation_item, Icons.Filled.Class)
object ExamList :
Screen("examList", R.string.exam_bottom_navigation_item, Icons.Filled.Dashboard)
object ScoreList :
Screen("scoreList", R.string.score_bottom_navigation_item, Icons.Filled.Score)
object Statistics :
Screen("statistics", R.string.statistics_bottom_navigation_item, Icons.Filled.Star)
}
val items = listOf(Screen.ClassList, Screen.ExamList, Screen.ScoreList, Screen.Statistics)
One of the page are like this, using Flow.collectAsState() to convert data Flow from Room to Composable State
#Composable
fun ClassPage(
viewModel: MainViewModel,
modifier: Modifier = Modifier
) {
val classesData by viewModel.classList.collectAsState(listOf())
ClassList(classesData, modifier)
}
#Composable
fun ClassList(classesData: List<ClassData>, modifier: Modifier = Modifier) {
val listState = rememberLazyListState()
Column(
modifier = modifier
.fillMaxWidth()
.background(MaterialTheme.colors.background)
) {
LazyColumn(state = listState, contentPadding = PaddingValues(4.dp)) {
items(
items = classesData,
itemContent = { classData -> ClassItem(classData = classData, selectClass = {}) })
}
}
}
And it did build a workable MainActivity with BottomNavigation, but when I switch between BottomNavigation buttons quickly, my app crashed and I get Error Log like below:
Process: ***, PID: 26668
java.lang.IllegalStateException: You cannot access the NavBackStackEntry's ViewModels until it is added to the NavController's back stack (i.e., the Lifecycle of the NavBackStackEntry reaches the CREATED state).
at androidx.navigation.NavBackStackEntry.getViewModelStore(NavBackStackEntry.kt:174)
at androidx.lifecycle.ViewModelProvider.<init>(ViewModelProvider.java:99)
at androidx.lifecycle.viewmodel.compose.ViewModelKt.get(ViewModel.kt:82)
at androidx.lifecycle.viewmodel.compose.ViewModelKt.viewModel(ViewModel.kt:72)
at androidx.navigation.compose.NavBackStackEntryProviderKt.SaveableStateProvider(NavBackStackEntryProvider.kt:86)
at androidx.navigation.compose.NavBackStackEntryProviderKt.access$SaveableStateProvider(NavBackStackEntryProvider.kt:1)
at androidx.navigation.compose.NavBackStackEntryProviderKt$LocalOwnersProvider$1.invoke(NavBackStackEntryProvider.kt:51)
at androidx.navigation.compose.NavBackStackEntryProviderKt$LocalOwnersProvider$1.invoke(NavBackStackEntryProvider.kt:50)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:215)
at androidx.navigation.compose.NavBackStackEntryProviderKt.LocalOwnersProvider(NavBackStackEntryProvider.kt:46)
at androidx.navigation.compose.NavHostKt$NavHost$3.invoke(NavHost.kt:132)
at androidx.navigation.compose.NavHostKt$NavHost$3.invoke(NavHost.kt:131)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:116)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at androidx.compose.animation.CrossfadeKt$Crossfade$1$1.invoke(Crossfade.kt:74)
at androidx.compose.animation.CrossfadeKt$Crossfade$1$1.invoke(Crossfade.kt:69)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
at androidx.compose.animation.CrossfadeKt.Crossfade(Crossfade.kt:86)
at androidx.navigation.compose.NavHostKt.NavHost(NavHost.kt:131)
at androidx.navigation.compose.NavHostKt$NavHost$4.invoke(Unknown Source:13)
at androidx.navigation.compose.NavHostKt$NavHost$4.invoke(Unknown Source:10)
at androidx.compose.runtime.RecomposeScopeImpl.compose(RecomposeScopeImpl.kt:140)
at androidx.compose.runtime.ComposerImpl.recomposeToGroupEnd(Composer.kt:2156)
at androidx.compose.runtime.ComposerImpl.skipCurrentGroup(Composer.kt:2399)
at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:2580)
at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:2573)
at androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(SnapshotState.kt:540)
at androidx.compose.runtime.ComposerImpl.doCompose(Composer.kt:2566)
at androidx.compose.runtime.ComposerImpl.recompose$runtime_release(Composer.kt:2542)
at androidx.compose.runtime.CompositionImpl.recompose(Composition.kt:613)
at androidx.compose.runtime.Recomposer.performRecompose(Recomposer.kt:764)
at androidx.compose.runtime.Recomposer.access$performRecompose(Recomposer.kt:103)
at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:447)
at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:416)
at androidx.compose.ui.platform.AndroidUiFrameClock$withFrameNanos$2$callback$1.doFrame(AndroidUiFrameClock.android.kt:34)
at androidx.compose.ui.platform.AndroidUiDispatcher.performFrameDispatch(AndroidUiDispatcher.android.kt:109)
at androidx.compose.ui.platform.AndroidUiDispatcher.access$performFrameDispatch(AndroidUiDispatcher.android.kt:41)
at androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.doFrame(AndroidUiDispatcher.android.kt:69)
2021-08-21 17:45:08.153 26668-26668/com.zjuqsc.mobile4 E/AndroidRuntime: at android.view.Choreographer$CallbackRecord.run(Choreographer.java:970)
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:7660)
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)
I use debug breakpoint to see what happened, and it turns out that it use getViewModelStore when NavBackStackEntry reach Lifecycle.State.DESTROYED state, and I have no idea about how to fix it. I would be very grateful if anyone could help me
update to 2.4.0-alpha07 fixes my problem
Try to initialise your viewmodel in the main activity like val viewModel by viewModels<MainViewModel>()
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"
}
}