#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. Но я не уверен, как это сделать с этими библиотеками.