1

Hello, I am currently preparing one simple demo example of MVVM with Coroutines and I am facing the below issue. Please have a look into the code and what wrong with the code.

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.android.mvvmcoroutine.development, PID: 18974
    java.lang.RuntimeException: Failed to invoke public io.reactivex.Observable() with no args
        at com.google.gson.internal.ConstructorConstructor$3.construct(ConstructorConstructor.java:113)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:212)
        at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:39)
        at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:27)
        at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:225)
        at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:121)
        at okhttp3.RealCall$AsyncCall.run(RealCall.kt:138)
        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:919)
     Caused by: java.lang.InstantiationException: Can't instantiate abstract class io.reactivex.Observable
        at java.lang.reflect.Constructor.newInstance0(Native Method)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:343)
        at com.google.gson.internal.ConstructorConstructor$3.construct(ConstructorConstructor.java:110)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:212) 
        at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:39) 
        at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:27) 
        at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:225) 
        at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:121) 
        at okhttp3.RealCall$AsyncCall.run(RealCall.kt:138) 
        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:919)

Dependency Configuration

'rxJavaVersion'           : 'io.reactivex.rxjava2:rxjava:2.1.15'
'rxAndroidVersion'        : 'io.reactivex.rxjava2:rxandroid:2.1.1'
'rxKotlinVersion'         : 'io.reactivex.rxjava2:rxkotlin:2.4.0'
'gsonVersion'             : 'com.google.code.gson:gson:2.8.2'
'retrofitVersion'         : 'com.squareup.retrofit2:retrofit:2.6.2'
                          : 'com.squareup.retrofit2:adapter-rxjava2:2.6.2'
                          : 'com.squareup.retrofit2:converter-gson:2.6.2'
                          : 'com.squareup.retrofit2:converter-scalars:2.6.2'
'daggerVersion'           : 'com.google.dagger:dagger:2.23.2'
                          : 'com.google.dagger:dagger-android-support:2.23.2'
                          : 'com.google.dagger:dagger-compiler:2.23.2'
                          : 'com.google.dagger:dagger-android-processor:2.23.2'
'assistedInjectVersion'   : 'com.squareup.inject:assisted-inject-annotations-dagger2:0.3.2'
                          : 'com.squareup.inject:assisted-inject-processor-dagger2:0.3.2'
'okHttpVersion'           : 'com.squareup.okhttp3:okhttp:4.2.0'
                          : 'com.squareup.okhttp3:logging-interceptor:4.2.0'

Application code as below

@ApplicationClass

open class App : MultiDexApplication(), Application.ActivityLifecycleCallbacks, HasActivityInjector {

    private var runningActivityCount: Int = 0

    @Inject
    lateinit var activityDispatchingAndroidInjector: DispatchingAndroidInjector<Activity>

    override fun activityInjector(): AndroidInjector<Activity> {
        return activityDispatchingAndroidInjector
    }

    override fun onActivityPaused(p0: Activity) {}

    override fun onActivityStarted(p0: Activity) {}

    override fun onActivityDestroyed(p0: Activity) {
        runningActivityCount -= 1
        if (runningActivityCount == 0)
            Timber.d("Application :: onActivityDestroyed()")
    }

    override fun onActivitySaveInstanceState(p0: Activity, p1: Bundle) {}

    override fun onActivityStopped(p0: Activity) {}

    override fun onActivityCreated(p0: Activity, p1: Bundle?) {
        runningActivityCount += 1
    }

    override fun onActivityResumed(p0: Activity) {}

    override fun onCreate() {
        super.onCreate()
        DaggerAppComponent.builder()
            .application(this)
            .build()
            .inject(this)
    }
}

@AppComponentInterface

@Singleton
@Component(
    modules = [
        (AndroidInjectionModule::class),
        (ActivityBuilder::class),
        (ApplicationModule::class),
        (APIModule::class),
        (RepositoryModule::class),
        (PostScreenModule::class)
    ]
)
interface AppComponent {
    fun inject(app: App)
    @Component.Builder
    interface Builder {
        @BindsInstance
        fun application(app: Application): Builder

        fun build(): AppComponent
    }
}

@ActivityBuilder

@Module
abstract class ActivityBuilder {
    @ContributesAndroidInjector(modules = [(PostScreenModule::class)])
    internal abstract fun bindPostScreen(): PostScreen
}

@ApplicaitonModule

@Module
class ApplicationModule {

    @Provides
    @Singleton
    internal fun provideContext(application: Application): Context {
        return application
    }

    @Provides
    @Singleton
    internal fun provideCommonPreference(context: Context): CommonPreferences {
        return CommonPreferences(context)
    }
}

@APIModule

@Module
class APIModule {

    @Provides
    @Singleton
    internal fun provideHeaderInterceptor(mContext: Context): HeaderInterceptor {
        return HeaderInterceptor(mContext)
    }

    @Provides
    @Singleton
    internal fun provideOkHttp(mContext: Context): OkHttpClient {
        val interceptor = HttpLoggingInterceptor()
        interceptor.level = HttpLoggingInterceptor.Level.BODY
        return OkHttpClient.Builder()
            .readTimeout(TIMEOUT, TimeUnit.SECONDS)
            .connectTimeout(TIMEOUT, TimeUnit.SECONDS)
            .writeTimeout(TIMEOUT, TimeUnit.SECONDS)
            .addInterceptor(HeaderInterceptor(mContext))
            .addInterceptor(interceptor)
            .build()
    }

    @Provides
    @Singleton
    internal fun provideGsonConverterFactory(): GsonConverterFactory {
        return GsonConverterFactory.create()
    }

    @Provides
    @Singleton
    internal fun provideScalarsConverterFactory(): ScalarsConverterFactory {
        return ScalarsConverterFactory.create()
    }

    @Provides
    @Singleton
    internal fun provideRxJava2CallAdapterFactory(): RxJava2CallAdapterFactory {
        return RxJava2CallAdapterFactory.createAsync()
    }

    @Provides
    @Singleton
    internal fun provideRetrofit(
        okHttpClient: OkHttpClient,
        gsonConverterFactory: GsonConverterFactory,
        scalarsConverterFactory: ScalarsConverterFactory,
        rxJava2CallAdapterFactory: RxJava2CallAdapterFactory
    ): Retrofit {
        return Retrofit.Builder()
            .addConverterFactory(gsonConverterFactory)
            .addConverterFactory(scalarsConverterFactory)
            .addCallAdapterFactory(rxJava2CallAdapterFactory)
            .client(okHttpClient)
            .baseUrl("https://jsonplaceholder.typicode.com")
            .build()
    }

    @Provides
    @Singleton
    internal fun provideAPIUrls(retrofit: Retrofit): APIUrls {
        return retrofit.create(APIUrls::class.java)
    }
}

@HeaderInterceptorClass

class HeaderInterceptor @Inject constructor(private val mContext: Context) : Interceptor {

    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        val token = CommonPreferences(mContext).token

        val request: Request

        if (token.isNotEmpty()) {
            val androidDeviceId = CommonPreferences(mContext).deviceUniqueId
            val appVersion = BuildConfig.VERSION_NAME
            val buildVersion = BuildConfig.VERSION_CODE
            var lastSyncDate = "2019-11-29 12:00:00"

            if (CommonPreferences(mContext).lastSyncTime.isNotEmpty()) {
                lastSyncDate = CommonPreferences(mContext).lastSyncTime
            }

            request =
                chain.request().newBuilder()
                    .addHeader(HEADER_X_ACCESS_TOKEN_KEY, token)
                    .addHeader(HEADER_DEVICE_MASTER_ID_KEY, androidDeviceId)
                    .addHeader(HEADER_DEVICE_ID_KEY, androidDeviceId)
                    .addHeader(HEADER_DEVICE_NAME_KEY, getDeviceName())
                    .addHeader(HEADER_DEVICE_TYPE_KEY, HEADER_DEVICE_TYPE_VALUE)
                    .addHeader(HEADER_PUSH_REGISTRATION_ID_KEY, token)
                    .addHeader(HEADER_APP_VERSION_KEY, appVersion)
                    .addHeader(HEADER_API_VERSION_KEY, HEADER_API_VERSION_VALUE)
                    .addHeader(HEADER_OS_VERSION_KEY, android.os.Build.VERSION.RELEASE)
                    .addHeader(HEADER_USER_ID_KEY, CommonPreferences(mContext).userId)
                    .addHeader(HEADER_SYNC_DATE_KEY, lastSyncDate)
                    .addHeader(HEADER_BUILD_VERSION, buildVersion.toString())
                    .build()

            return chain.proceed(request)
        }

        return chain.proceed(chain.request())
    }
}

@RepositoryModule

@Module
class RepositoryModule {

    @Provides
    @Singleton
    internal fun provideAuthRepository(apiUrls: APIUrls): PostRepository {
        return PostRepository(apiUrls)
    }
}

@PostScreenModule

@Module
class PostScreenModule {
    @Provides
    internal fun providePostViewModel(
        application: Application,
        postRepository: PostRepository
    ): PostViewModel {
        return PostViewModel(application, postRepository)
    }
}

@PostScreenActivity

class PostScreen : BaseActivity<ActivityPostScreenBinding, PostViewModel>() {

    @Inject
    lateinit var mPostViewModel: PostViewModel

    override fun getLayoutId(): Int = R.layout.activity_post_screen

    override fun getBindingVariable(): Int = BR.viewModel

    override fun getViewModel(): PostViewModel = mPostViewModel

    override fun initialization() {
        mPostViewModel.viewModelScope.launch(Dispatchers.Main) {
            mPostViewModel.postProcess()
        }
    }
}

@PostScreenViewModel

class PostViewModel @Inject constructor(
    application: Application,
    val postRepository: PostRepository
) : BaseViewModel(application, postRepository) {

    var loading = MutableLiveData<Boolean>()

    init {
        loading.value = false
    }

    suspend fun postProcess() {
        withContext(Dispatchers.IO) {
            postRepository.getPostData()
        }
    }
}

@PostRepositoryClass

@Singleton
class PostRepository @Inject constructor(private val apiUrls: APIUrls) {

    @SuppressLint("CheckResult")
    suspend fun getPostData() {
        apiUrls.getData("https://jsonplaceholder.typicode.com/posts/1")
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe({ data ->
                println("DATA RECEIVED ::: $data")
            }, { e ->
                Timber.d("Exception :: ${e.message}")
            })
    }

@APIUrlsInterface

@Singleton
interface APIUrls {
    @GET
    suspend fun getData(@Url url: String): Observable<PostResponse>
}

@PostResponseModel

data class PostResponse(
    @SerializedName("userId")
    val userId: Int,
    @SerializedName("id")
    val id: Int,
    @SerializedName("title")
    val title: String,
    @SerializedName("body")
    val body: String
) : Serializable
Maulik Sinroja
  • 191
  • 1
  • 6
  • 17
  • Looks like one of your classes not listed has an `Observable` field you try to load from json. – akarnokd Nov 29 '19 at 19:53
  • @akarnokd could you please elaborate it? – Maulik Sinroja Nov 30 '19 at 06:14
  • Could you provide a standalone example project that demonstrates this problem (for example, on Github)? If this is part of a private project, just create a fresh one with only the relevant parts so it can be examined in its entirety. – akarnokd Dec 01 '19 at 09:13
  • @akarnokd I have added you as collaborators on the GitHub repository. Please clone it with your credential and help me out. – Maulik Sinroja Dec 02 '19 at 05:47
  • @akarnokd Please check this https://github.com/MakSinroja/mvvm_coroutines.git – Maulik Sinroja Dec 02 '19 at 07:34
  • Oh, it was you. I saw a private repo with tech names I don't support and declined it. – akarnokd Dec 02 '19 at 10:53
  • @akarnokd could you please check it again, I send it again? – Maulik Sinroja Dec 02 '19 at 11:02
  • I see, the problem is you have a suspend function to return an `Observable`, probably from an earlier RxJava approach. You need to declare the `APIUrls.getData` with a plain `PostResponse` or drop the `suspend` prefix and setup an RxJava binding for retrofit. – akarnokd Dec 02 '19 at 11:16
  • Ohhh... It's worked, I thought It would work earlier RxJava approaches. So, I put it but now I am clear to use where used suspend. Thank you for help and support to guide me and resolving the issue. for me, its appreciate help. – Maulik Sinroja Dec 02 '19 at 11:35

1 Answers1

7

The problem turns out to be having the interface mehtod as both suspend and Observable. Retrofit puts priority on the suspend and thinks the return type should be Observable, but Observable is not a serializable data container.

The solution is to either remove suspend or unwrap the datatype of the API call:

@Singleton
interface APIUrls {
    @GET
    fun getData(@Url url: String): Observable<PostResponse>

    // or

    suspend fun getData(@Url url: String): PostResponse
} 
akarnokd
  • 69,132
  • 14
  • 157
  • 192