I created a navigation system to navigate to correct fragment based on Destination object which I send with Intent as Parcelable extra
#Serializable
sealed class Destination : Parcelable {
abstract val destinationType: Type
abstract val destinationFragment: Fragment
enum class Type(val defaultDestination: Destination) {
DASHBOARD(Dashboard),
GRADE(Grade),
ATTENDANCE(Attendance),
EXAM(Exam),
TIMETABLE(Timetable()),
HOMEWORK(Homework),
NOTE(Note),
CONFERENCE(Conference),
SCHOOL_ANNOUNCEMENT(SchoolAnnouncement),
SCHOOL(School),
LUCKY_NUMBER(More),
MORE(More),
MESSAGE(Message);
}
#Parcelize
#Serializable
object Dashboard : Destination() {
override val destinationType get() = Type.DASHBOARD
override val destinationFragment get() = DashboardFragment.newInstance()
}
#Parcelize
#Serializable
object Grade : Destination() {
override val destinationType get() = Type.GRADE
override val destinationFragment get() = GradeFragment.newInstance()
}
#Parcelize
#Serializable
object Attendance : Destination() {
override val destinationType get() = Type.ATTENDANCE
override val destinationFragment get() = AttendanceFragment.newInstance()
}
#Parcelize
#Serializable
object Exam : Destination() {
override val destinationType get() = Type.EXAM
override val destinationFragment get() = ExamFragment.newInstance()
}
#Parcelize
#Serializable
data class Timetable(
#Serializable(with = LocalDateSerializer::class)
private val date: LocalDate? = null
) : Destination() {
override val destinationType get() = Type.TIMETABLE
override val destinationFragment get() = TimetableFragment.newInstance(date)
}
#Parcelize
#Serializable
object Homework : Destination() {
override val destinationType get() = Type.HOMEWORK
override val destinationFragment get() = HomeworkFragment.newInstance()
}
#Parcelize
#Serializable
object Note : Destination() {
override val destinationType get() = Type.NOTE
override val destinationFragment get() = NoteFragment.newInstance()
}
#Parcelize
#Serializable
object Conference : Destination() {
override val destinationType get() = Type.CONFERENCE
override val destinationFragment get() = ConferenceFragment.newInstance()
}
#Parcelize
#Serializable
object SchoolAnnouncement : Destination() {
override val destinationType get() = Type.SCHOOL_ANNOUNCEMENT
override val destinationFragment get() = SchoolAnnouncementFragment.newInstance()
}
#Parcelize
#Serializable
object School : Destination() {
override val destinationType get() = Type.SCHOOL
override val destinationFragment get() = SchoolFragment.newInstance()
}
#Parcelize
#Serializable
object LuckyNumber : Destination() {
override val destinationType get() = Type.LUCKY_NUMBER
override val destinationFragment get() = LuckyNumberFragment.newInstance()
}
#Parcelize
#Serializable
object More : Destination() {
override val destinationType get() = Type.MORE
override val destinationFragment get() = MoreFragment.newInstance()
}
#Parcelize
#Serializable
object Message : Destination() {
override val destinationType get() = Type.MESSAGE
override val destinationFragment get() = MessageFragment.newInstance()
}
}
This works perfectly on my devices. I released an app to the Google Play store. I noticed later in Firebase that a small part of users have this error
Fatal Exception: java.lang.RuntimeException: Unable to start activity ComponentInfo{io.github.wulkanowy/io.github.wulkanowy.ui.modules.splash.SplashActivity}: java.lang.RuntimeException: Parcelable encountered IOException reading a Serializable object (name = io.github.wulkanowy.ui.modules.Destination$Exam)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3477)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3620)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
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:2183)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:241)
at android.app.ActivityThread.main(ActivityThread.java:7604)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:941)
Caused by java.lang.RuntimeException: Parcelable encountered IOException reading a Serializable object (name = io.github.wulkanowy.ui.modules.Destination$Exam)
at android.os.Parcel.readSerializable(Parcel.java:3136)
at android.os.Parcel.readValue(Parcel.java:2917)
at android.os.Parcel.readArrayMapInternal(Parcel.java:3244)
at android.os.BaseBundle.initializeFromParcelLocked(BaseBundle.java:292)
at android.os.BaseBundle.unparcel(BaseBundle.java:236)
at android.os.Bundle.getBundle(Bundle.java:924)
at com.google.firebase.messaging.FcmLifecycleCallbacks.logNotificationOpen(FcmLifecycleCallbacks.java:79)
at com.google.firebase.messaging.FcmLifecycleCallbacks.onActivityCreated(FcmLifecycleCallbacks.java:49)
at android.app.Application.dispatchActivityCreated(Application.java:373)
at android.app.Activity.dispatchActivityCreated(Activity.java:1206)
at android.app.Activity.onCreate(Activity.java:1479)
at androidx.core.app.ComponentActivity.onCreate(ComponentActivity.java:85)
at androidx.activity.ComponentActivity.onCreate(ComponentActivity.java:321)
at androidx.fragment.app.FragmentActivity.onCreate(FragmentActivity.java:249)
at io.github.wulkanowy.ui.base.BaseActivity.onCreate(BaseActivity.kt:38)
at io.github.wulkanowy.ui.modules.splash.SplashActivity.onCreate(SplashActivity.kt:44)
at android.app.Activity.performCreate(Activity.java:7822)
at android.app.Activity.performCreate(Activity.java:7811)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1328)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3452)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3620)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
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:2183)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:241)
at android.app.ActivityThread.main(ActivityThread.java:7604)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:941)
Caused by java.io.InvalidClassException: io.github.wulkanowy.ui.modules.Destination$Exam; class invalid for deserialization
at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:154)
at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:798)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1873)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1412)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:427)
at android.os.Parcel.readSerializable(Parcel.java:3134)
at android.os.Parcel.readValue(Parcel.java:2917)
at android.os.Parcel.readArrayMapInternal(Parcel.java:3244)
at android.os.BaseBundle.initializeFromParcelLocked(BaseBundle.java:292)
at android.os.BaseBundle.unparcel(BaseBundle.java:236)
at android.os.Bundle.getBundle(Bundle.java:924)
at com.google.firebase.messaging.FcmLifecycleCallbacks.logNotificationOpen(FcmLifecycleCallbacks.java:79)
at com.google.firebase.messaging.FcmLifecycleCallbacks.onActivityCreated(FcmLifecycleCallbacks.java:49)
at android.app.Application.dispatchActivityCreated(Application.java:373)
at android.app.Activity.dispatchActivityCreated(Activity.java:1206)
at android.app.Activity.onCreate(Activity.java:1479)
at androidx.core.app.ComponentActivity.onCreate(ComponentActivity.java:85)
at androidx.activity.ComponentActivity.onCreate(ComponentActivity.java:321)
at androidx.fragment.app.FragmentActivity.onCreate(FragmentActivity.java:249)
at io.github.wulkanowy.ui.base.BaseActivity.onCreate(BaseActivity.kt:38)
at io.github.wulkanowy.ui.modules.splash.SplashActivity.onCreate(SplashActivity.kt:44)
at android.app.Activity.performCreate(Activity.java:7822)
at android.app.Activity.performCreate(Activity.java:7811)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1328)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3452)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3620)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
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:2183)
at android.os.Handler.dispatchMessage(Handler.java:107)
at android.os.Looper.loop(Looper.java:241)
at android.app.ActivityThread.main(ActivityThread.java:7604)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:941)
I can't reproduce this error on my devices or on android emulator.
Link to the app's github repository
https://github.com/wulkanowy/wulkanowy
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.
I'm getting error "Can't access ViewModels from detached fragment" when onClick method implemented by AirportOnClickListener try do do anything with viewmodel (trigger event, set var etc). What's wrong with my code? I'm using hilt for DI
#AndroidEntryPoint
class SearchFragment #Inject constructor() : Fragment(), AirportOnClickListener {
#Inject
lateinit var adapter: RecyclerViewAdapter
private var _binding: FragmentAirportSearchBinding? = null
private val binding get() = _binding!!
private val airportSearchViewModel by viewModels<AirportSearchViewModel>()
private val args: SearchFragmentArgs by navArgs()
// recyclerview row click cause error "java.lang.IllegalStateException: Can't access ViewModels from detached fragment"
// when doing anything with airportSearchViewModel
override fun onClick(airport: Airport) {
airportSearchViewModel.setAirportTye(AirportType.Origin) //ERROR
airportSearchViewModel.triggerNavigation() //ERROR
Log.d("Selected airport", airport.name) //WORKS
}
class RecyclerViewAdapter #Inject constructor(private val airportOnClickListener: Provider<AirportOnClickListener>) :
RecyclerView.Adapter<SearchViewHolder>() {
var list = emptyList<Airport>()
set(value) {
field = value
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchViewHolder {
val binding = RowAirportSearchBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return SearchViewHolder(binding, airportOnClickListener.get())
}
override fun getItemCount(): Int {
return list.size
}
override fun onBindViewHolder(holder: SearchViewHolder, position: Int) {
holder.bind(list[position])
}
}
class SearchViewHolder(
private val binding: RowAirportSearchBinding,
private val airportOnClickListener: AirportOnClickListener,
) :
RecyclerView.ViewHolder(binding.root) {
fun bind(airport: Airport) {
with(binding) {
city.text = airport.city.name
country.text = airport.country.name
code.text = airport.code
root.setOnClickListener {
airportOnClickListener.onClick(airport)
}
}
}
}
#Module
#InstallIn(FragmentComponent::class)
abstract class AirportListenerModule {
#Binds
abstract fun bindClickListener(
searchFragment: SearchFragment,
): AirportOnClickListener
}
kotlin.UninitializedPropertyAccessException: lateinit property airportSearchViewModel has not been initialized
at com.jurgielewiczp.androidchallenge.views.search.SearchFragment.getAirportSearchViewModel(SearchFragment.kt:37)
at com.jurgielewiczp.androidchallenge.views.search.SearchFragment.onClick(SearchFragment.kt:44)
at com.jurgielewiczp.androidchallenge.views.search.SearchViewHolder.bind$lambda-1$lambda-0(RecyclerViewAdapter.kt:49)
at com.jurgielewiczp.androidchallenge.views.search.SearchViewHolder.$r8$lambda$dVFh158DngIJuL9pfu4VOonhCvg(Unknown Source:0)
at com.jurgielewiczp.androidchallenge.views.search.SearchViewHolder$$ExternalSyntheticLambda0.onClick(Unknown Source:4)
at android.view.View.performClick(View.java:6614)
at android.view.View.performClickInternal(View.java:6587)
at android.view.View.access$3100(View.java:787)
at android.view.View$PerformClick.run(View.java:26122)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:201)
at android.app.ActivityThread.main(ActivityThread.java:6831)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:927)
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'm making a call to a weather API and getting back the HourlyResponse that you see below. The hourly field maps an array of JSON objects to a list of HourWeatherEntry entity classes which I'm trying to store using Room. Now, in my RoomDatabase implementation, I've specified the custom type converters to be used for the List<Weather> field of the HourWeatherEntry class and despite that, I'm getting the SerializationException that I've pasted below. What am I missing, shouldn't the converters suffice? Why would I also need to define the Weather class' serialization methods for the class itself?
HourlyResponse.kt:
data class HourlyResponse(
val hourly: List<HourWeatherEntry>,
val lat: Double,
val lon: Double,
val timezone: String
)
HourWeatherEntry.kt
#Entity(tableName = "hour_weather")
data class HourWeatherEntry(
#SerializedName("weather")
val weatherInfo: List<Weather>,
#SerializedName("temp")
val temperature: Double,
#SerializedName("dt")
val time: Long
) {
#PrimaryKey(autoGenerate = true)
var id:Int = 0
var icon: String? = null
init {
icon = weatherInfo[0].icon
}
}
Weather.kt:
data class Weather(
val description: String,
val icon: String,
val id: Int,
val main: String
)
Converters.kt:
class Converters {
val gson = Gson()
#TypeConverter
fun weatherToString(weather: Weather): String = gson.toJson(weather)
#TypeConverter
fun weatherFromString(json: String): Weather = gson.fromJson(json, Weather::class.java)
#TypeConverter
fun fromWeatherList(value: List<Weather>): String = gson.toJson(value)
#TypeConverter
fun toWeatherList(value: String): List<Weather> = Json.decodeFromString(value)
}
ForecastDatabase.kt:
#Database(
entities = [CurrentWeatherEntry::class, HourWeatherEntry::class, WeekDayWeatherEntry::class, WeatherLocation::class],
version = 1
)
#TypeConverters(Converters::class)
abstract class ForecastDatabase : RoomDatabase() {
abstract fun currentWeatherDao(): CurrentWeatherDao
abstract fun hourWeatherDao(): HourWeatherDao
abstract fun weekDayWeatherDao(): WeekDayWeatherDao
abstract fun weatherLocationDao(): WeatherLocationDao
// Used to make sure that the ForecastDatabase class will be a singleton
companion object {
// Volatile == all of the threads will have immediate access to this property
#Volatile
private var instance: ForecastDatabase? = null
private val LOCK = Any() // dummy object for thread monitoring
operator fun invoke(context: Context) = instance ?: synchronized(LOCK) {
// If the instance var hasn't been initialized, call buildDatabase()
// and assign it the returned object from the function call (it)
instance ?: buildDatabase(context).also { instance = it }
}
/**
* Creates an instance of the ForecastDatabase class
* using Room.databaseBuilder().
*/
private fun buildDatabase(context: Context) =
Room.databaseBuilder(
context.applicationContext,
ForecastDatabase::class.java, "forecast.db"
)
//.addMigrations(MIGRATION_2_3) // specify an explicit Migration Technique
.fallbackToDestructiveMigration()
.build()
}
}
Exception log:
java.lang.RuntimeException: Exception while computing database live data.
at androidx.room.RoomTrackingLiveData$1.run(RoomTrackingLiveData.java:92)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:923)
Caused by: kotlinx.serialization.SerializationException: Serializer for class 'Weather' is not found.
Mark the class as #Serializable or provide the serializer explicitly.
at kotlinx.serialization.internal.Platform_commonKt.serializerNotRegistered(Platform.common.kt:91)
at kotlinx.serialization.internal.PlatformKt.platformSpecificSerializerNotRegistered(Platform.kt:29)
at kotlinx.serialization.SerializersKt__SerializersKt.serializer(Serializers.kt:60)
at kotlinx.serialization.SerializersKt.serializer(Unknown Source:1)
at kotlinx.serialization.SerializersKt__SerializersKt.builtinSerializer$SerializersKt__SerializersKt(Serializers.kt:96)
at kotlinx.serialization.SerializersKt__SerializersKt.serializerByKTypeImpl$SerializersKt__SerializersKt(Serializers.kt:84)
at kotlinx.serialization.SerializersKt__SerializersKt.serializer(Serializers.kt:59)
at kotlinx.serialization.SerializersKt.serializer(Unknown Source:1)
at com.nesoinode.flogaweather.model.network.typeconverters.Converters.toWeatherList(Converters.kt:25)
at com.nesoinode.flogaweather.model.db.dao.HourWeatherDao_Impl$3.call(HourWeatherDao_Impl.java:115)
at com.nesoinode.flogaweather.model.db.dao.HourWeatherDao_Impl$3.call(HourWeatherDao_Impl.java:99)
at androidx.room.RoomTrackingLiveData$1.run(RoomTrackingLiveData.java:90)
... 3 more
I am developing a new book android app but app crashes when I am testing the code getting following exceptions from logcat
java.lang.RuntimeException: Unable to start activity
ComponentInfo{com.example.bookkeepers/com.example.bookkeepers.MainActivity}:
java.lang.RuntimeException: Cannot create an instance of class
com.example.bookkeepers.BookViewModel at
android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2843)
at
android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2921)
at android.app.ActivityThread.-wrap11(Unknown Source:0) at
android.app.ActivityThread$H.handleMessage(ActivityThread.java:1639)
at android.os.Handler.dispatchMessage(Handler.java:106) at
android.os.Looper.loop(Looper.java:176) at
android.app.ActivityThread.main(ActivityThread.java:6662) at
java.lang.reflect.Method.invoke(Native Method) at
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873) Caused
by: java.lang.RuntimeException: Cannot create an instance of class
com.example.bookkeepers.BookViewModel at
androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.java:208)
at androidx.l
ifecycle.ViewModelProvider.get(ViewModelProvider.java:135) at
androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:103)
at com.example.bookkeepers.MainActivity.onCreate(MainActivity.kt:30)
at android.app.Activity.performCreate(Activity.java:7074) at
android.app.Activity.performCreate(Activity.java:7065) at
android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1214)
at
android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2796)
... 9 more Caused by: java.lang.reflect.InvocationTargetException at
java.lang.reflect.Constructor.newInstance0(Native Method) at
java.lang.reflect.Constructor.newInstance(Constructor.java:334) at
androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.java:200)
... 16 more Caused by: java.lang.RuntimeException: cannot find
implementation for com.example.bookkeepers.BookRoomDatabase.
BookRoomDatabase_Impl does not exist at
androidx.room.Room.getGeneratedImplementation(Room.java:94) at
androidx.room.RoomDatabase$Builder.build(RoomDatabase.java:952) at
com.example.bookkeepers.BookRoomDatabase$Companion.getDatabase(BookRoomDatabase.kt:24)
at com.example.bookkeepers.BookViewModel.(BookViewModel.kt:12)
... 19 more
below my BookViewModel class which extends AndroidViewModel class
class BookViewModel(application: Application) : AndroidViewModel(application) {
private val bookDao: BookDao
init {
val bookDb = BookRoomDatabase.getDatabase(application)
bookDao = bookDb!!.bookDao()
}
fun insert(book: Book) {
InsertAsyncTask(bookDao).execute(book)
}
companion object {
private class InsertAsyncTask(private val bookDao: BookDao) :
AsyncTask<Book, Void, Void>() {
override fun doInBackground(vararg books: Book): Void? {
bookDao.insert(books[0])
return null
}
}
}}
below MainActivity.kt
class
MainActivity : AppCompatActivity() {
lateinit var bookViewModel: BookViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
fab.setOnClickListener { view ->
val intent = Intent(this, NewBookActivity::class.java)
startActivityForResult(intent, NEW_NOTE_ACTIVITY_REQUEST_CODE)
}
bookViewModel = ViewModelProviders.of(this).get(BookViewModel::class.java)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == NEW_NOTE_ACTIVITY_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
val id = UUID.randomUUID().toString()
val authorName = data!!.getStringExtra(NewBookActivity.NEW_AUTHOR)
val bookName = data!!.getStringExtra(NewBookActivity.NEW_BOOK)
val book = Book(id, authorName, bookName)
bookViewModel.insert(book)
Toast.makeText(
applicationContext,
R.string.saved,
Toast.LENGTH_LONG
).show()
} else {
Toast.makeText(
applicationContext,
R.string.not_saved,
Toast.LENGTH_LONG
).show()
}
}
companion object{
private const val NEW_NOTE_ACTIVITY_REQUEST_CODE = 1
}}
below database class
#Database(entities = [Book::class], version = 1)
abstract class BookRoomDatabase :RoomDatabase() {
abstract fun bookDao():BookDao
companion object {
private var bookRoomInstance:BookRoomDatabase? = null
fun getDatabase(context: Context): BookRoomDatabase? {
if (bookRoomInstance == null)
synchronized(BookRoomDatabase::class.java){
if(bookRoomInstance == null){
bookRoomInstance = Room.databaseBuilder<BookRoomDatabase>(context.applicationContext,
BookRoomDatabase::class.java, "book_database")
.build()
}
}
return bookRoomInstance
}
}}
below dao class
#Dao
interface BookDao {
#Insert
fun insert(book: Book)
}
below book entity class
#Entity(tableName = "books")
class Book(#PrimaryKey val id: String,
#ColumnInfo(name = "author")
val author: String,
val book:String) {
}
The idea behind it that you are using kotlin and in your app build.gradle you should be applying kapt plugin apply plugin 'kotlin-kapt' and using kapt instead of annotationProcessor
implementation "android.arch.lifecycle:extensions:$lifecycle_version"
kapt "android.arch.lifecycle:compiler:$lifecycle_version"
implementation "android.arch.persistence.room:runtime:$room_version"
kapt "android.arch.persistence.room:compiler:$room_version"
Check this answer https://stackoverflow.com/a/50487165/4614550
The problem is that you're trying to instantiate the ViewModel without providing the required arguments application: Application.
I posted an answer that solves this issue here: https://stackoverflow.com/a/65004171/13834895.