Исключение ClassCastException в NetworkBoundResource

#android #kotlin #aws-lambda #gson #aws-sdk

#Android #kotlin #aws-лямбда #gson #aws-sdk

Вопрос:

Я следую «лучшим практикам» (?) из примера GitHub: https://github.com/android/architecture-components-samples/blob/master/GithubBrowserSample/app/src/main/java/com/android/example/github/repository/NetworkBoundResource.kt

Я получаю сообщение об ошибке

java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap не может быть преобразован в com.xxx.yyy.data.model.TestUser

Я пытался использовать Gson для преобразования в JSON и обратно в общий RequestType, но это тоже не работает. Данные возвращаются просто отлично. Если я приведу ответ в NetworkBoundResource , тогда это сработает, но это противоречит цели дженериков. Я также использую aws-android-sdk для вызова lambda, что создает ОГРОМНУЮ проблему, но это AWS для вас

TestUser

 public class TestUser {
    private String username;

    public TestUser() {

    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}
  

NetworkBoundResource

 abstract class NetworkBoundResource<ResultType, RequestType>
@MainThread constructor(private val appExecutors: AppExecutors) {

    private val result = MediatorLiveData<Resource<ResultType>>()

    init {
        result.value = Resource.loading(null)

        val dbSource = loadFromDb()
        result.addSource(dbSource) { data ->
            result.removeSource(dbSource)
            if (shouldFetch(data)) {
                Log.e("NetworkBoundResource", "fetching from network")
                appExecutors.networkIO().execute {
                    fetchFromNetwork(dbSource)
                }
            } else {
                Log.e("NetworkBoundResource", "not fetching from network")
                result.addSource(dbSource) { newData ->
                    setValue(Resource.success(newData))
                }
            }
        }
    }

    @MainThread
    private fun setValue(newValue: Resource<ResultType>) {
        if (result.value != newValue) {
            result.value = newValue
        }
    }

    private fun fetchFromNetwork(dbSource: LiveData<ResultType>) {
        val apiResponse = MutableLiveData<ApiResponse<RequestType>>()

        // This is super dumb, but can't createCall() on the main thread 🤦
        var lambdaResponse: LambdaResponse<RequestType>? = null
        var exception: Exception? = null

        try {
            lambdaResponse = createCall()
        } catch (e: Exception) {
            exception = e
        }

        appExecutors.mainThread().execute {

            // Can't setValue on a background thread 🤦
            if (exception != null) {
                apiResponse.value = ApiResponse.create(exception)
            } else if (lambdaResponse != null) {
                apiResponse.value = ApiResponse.create(lambdaResponse)
            }

            // we re-attach dbSource as a new source, it will dispatch its latest value quickly
            result.addSource(dbSource) { newData ->
                setValue(Resource.loading(newData))
            }
            result.addSource(apiResponse) { response ->
                result.removeSource(apiResponse)
                result.removeSource(dbSource)
                when (response) {
                    is ApiSuccessResponse -> {
                        appExecutors.diskIO().execute {
                            val x = processResponse(response)

                            // FAILING HERE
                            saveCallResult(x)
                            appExecutors.mainThread().execute {
                                // we specially request a new live data,
                                // otherwise we will get immediately last cached value,
                                // which may not be updated with latest results received from network.
                                result.addSource(loadFromDb()) { newData ->
                                    setValue(Resource.success(newData))
                                }
                            }
                        }
                    }
                    is ApiEmptyResponse -> {
                        appExecutors.mainThread().execute {
                            // reload from disk whatever we had
                            result.addSource(loadFromDb()) { newData ->
                                setValue(Resource.success(newData))
                            }
                        }
                    }
                    is ApiErrorResponse -> {
                        onFetchFailed()
                        result.addSource(dbSource) { newData ->
                            setValue(Resource.error(response.errorMessage, newData))
                        }
                    }
                }
            }
        }
    }

    protected open fun onFetchFailed() {
        Log.e("NetworkBoundResource", "onFetchFailed")
    }

    fun asLiveData() = result as LiveData<Resource<ResultType>>

    @WorkerThread
    protected open fun processResponse(response: ApiSuccessResponse<RequestType>) = response.body

    @WorkerThread
    protected abstract fun saveCallResult(item: RequestType)

    @MainThread
    protected abstract fun shouldFetch(data: ResultType?): Boolean

    @MainThread
    protected abstract fun loadFromDb(): LiveData<ResultType>

    @MainThread
    protected abstract fun createCall(): LambdaResponse<RequestType>
}
  

LoginDataSource

 fun auth(authRequest: AuthRequest): LiveData<Resource<User>> {
    return object : NetworkBoundResource<User, TestUser>(appExecutors) {
        override fun saveCallResult(item: TestUser) {
            Log.e("username", item.username)
            // Log.e("saveCallResult", "saving user to db: ${item.federatedIdentity}")
            // userDao.insertAll(item)
        }

        override fun shouldFetch(data: User?): Boolean {
            val fetch = data == null
            Log.e("shouldFetch", "$fetch")
            return true
        }

        override fun loadFromDb(): LiveData<User> {
            Log.e("loadFromDb", "findById: ${authRequest.federatedIdentity}")
            return userDao.findById(authRequest.federatedIdentity)
        }

        override fun createCall(): LambdaResponse<TestUser> {
            Log.e("createCall", "authenticating user: ${authRequest.federatedIdentity}")
            return authApiService.auth(authRequest)
        }
    }.asLiveData()
}
  

Комментарии:

1. Можете ли вы также вставить свой ответ API вместе с authApiService кодом интерфейса?

2. Я не знаком с библиотеками, которые вы используете, но проблема может заключаться в том, что вы используете переменную типа ( RequestType ?) в качестве типа для десериализации Gson. Из-за стирания типа Java во время выполнения тип переменной type неизвестен, поэтому Gson десериализует ее как объект ( com.google.gson.internal.LinkedTreeMap это внутреннее представление Gson для этого). Вам либо каким-то образом придется передать ожидаемый тип в Gson; или, если тип динамический, каким-то образом закодируйте его в данных JSON. Но я не уверен, как это сделать с этими библиотеками.